2010-09-14 7 views
14

trovo che nel seguente frammento di codicecambiando il valore const in C

const int i = 2; 
const int* ptr1= &i; 
int* ptr2 = (int*)ptr1; 
*ptr2 =3; 

s' i valore cambia a 3. Quello che ho potuto di conoscere è il motivo per cui è questo permesso. Quali sono le situazioni in cui questo potrebbe diventare utile?

+13

Perché C è l'incarnazione perfetta di una digitazione debole. "I tipi sono solo per divertimento, se non ti piace il tipo di qualche cosa, basta lanciarlo";) – delnan

+1

La legge di Osborn http://www.anvari.org/fortune/Fortune_Cookies/95_osborn-s-law-variables-won- t.html – Jaydee

+2

Perché C & C++ ti consente di fare tutto ciò che vuoi, anche se vuoi fare qualcosa di veramente, davvero stupido. –

risposta

30

È consentito perché è stata annullata la costanza di ptr1 gettandola su un puntatore non const. Questo è il motivo per cui i lanci possono essere molto pericolosi.

Nota che alcuni compilatori, come GCC, non vi permetterà di lanciare lo stato di assenza const come questo.

+0

+1 buona risposta. –

+1

Si noti che questo codice potrebbe bloccarsi se il valore const è memorizzato nella memoria di sola lettura. –

+1

My gcc (4.4.3) accetta il codice senza alcun reclamo. compilatore di tastiera codici accetta anche (http://codepad.org/V9Oi65Cf) come fa Ideone di (http://ideone.com/tjCq7) – pmg

12

Hai rotto la garanzia di costanza giocando a trucchi puntatore. Non è garantito che funzioni tutto il tempo e possa invocare quasi ogni comportamento a seconda del sistema/OS/compilatore a cui lo lanci.

Non farlo.

O almeno non farlo a meno che non si sappia davvero cosa si sta facendo e anche in quel caso capisca che non è minimamente portatile.

5

const significa davvero "readonly".

Come hai scoperto, il valore degli oggetti const può cambiare, ma devi usare metodi subdoli per farlo. E mentre usi questi metodi subdoli invochi il comportamento non definito .

+1

@Konrad: In C e C++ è un comportamento indefinito modificare un oggetto che contiene una qualifica 'const' nella sua definizione. Questi oggetti potrebbero trovarsi potenzialmente in una memoria di sola lettura. –

+0

@Bart: hai ragione, naturalmente ... in qualche modo ho ignorato che si trattava di una definizione di variabile. –

2

Funziona perché si è espressamente escluso il valore const di punta. Mentre ptr1 è un puntatore a un const int, ptr2 è un puntatore a un int, quindi il suo pointee è modificabile.

Ci sono pochissimi buoni motivi per farlo, ma si può trovare un caso in cui evita la duplicazione del codice. Per esempio:

const char* letter_at(char* input, int position) 
{ 
    ... stuff ... 
    return &found_char; 
} 

char* editable_letter_at(char* input, int position) 
{ 
    return (char*)(letter_at(input, position)); 
} 

(esempio un po 'martoriato dalla esempio C++ al punto 3 della Effective C++ 3 °)

2

Se avete intenzione di gettare via constness in un programma C++, si prega di utilizzare uno stile più C++ di casting:

int *ptr2 = const_cast<int*>(ptr1); 

Se non esegue in problemi legati a questo tipo di casting (si, è sempre fare) allora si può trovare dove è accaduto molto rapidamente cercando "const_cast" piuttosto che cercare ogni combinazione sotto il sole. Inoltre, aiuterà gli altri nostri che potrebbero o non potrebbero venire dopo di te.

Ci sono solo alcune situazioni in cui ho potuto vedere questo essere utile. La maggior parte di loro sono casi angolari. Eviterei questo a tutti i costi se stai sviluppando in C++.

10

"ammessi" è l'opposto di "impedito", ma è anche il contrario di "proibito". Hai visto che la modifica del tuo oggetto const non è impedita, ma questo non significa esattamente che sia permesso.

Modifica di un oggetto const non è "consentito", nel senso di essere "permessa". Il comportamento del tuo programma non è definito dallo standard (vedi 6.7.3/5). Accade semplicemente che durante l'implementazione, in quella esecuzione, abbia visto il valore 3. In un'altra implementazione o in un altro giorno, potresti vedere un risultato diverso.

Tuttavia, non è "prevenuto", perché con il modo in cui funziona C, rilevarlo al momento della compilazione è un problema di arresto. Rilevarlo in fase di esecuzione richiede controlli aggiuntivi su tutti gli accessi alla memoria. Lo standard è progettato per non imporre un sovraccarico sulle implementazioni.

Il motivo per cui la condivisione di const è supportata, è perché se si dispone di un puntatore const su un oggetto non const, la lingua consente (in entrambi i sensi) di modificare quell'oggetto. Per fare ciò è necessario sbarazzarsi del qualificatore const. La conseguenza di ciò è che i programmatori possono anche scartare i qualificatori const dai puntatori agli oggetti che sono effettivamente const.

Ecco una (leggermente stupida) esempio di codice che scarta un qualificatore const per questo motivo:

typedef struct { 
    const char *stringdata; 
    int refcount; 
} atom; 

// returns const, because clients aren't allowed to directly modify atoms, 
// just read them 
const atom *getAtom(const char *s) { 
    atom *a = lookup_in_global_collection_of_atoms(s); 
    if (a == 0) { 
     // error-handling omitted 
     atom *a = malloc(sizeof(atom)); 
     a->stringdata = strdup(s); 
     a->refcount = 1; 
     insert_in_global_collection_of_atoms(a); 
    } else { 
     a->refcount++; 
    } 
    return a; 
} 

// takes const, because that's what the client has 
void derefAtom(const atom *a) { 
    atom *tmp = (atom*)a; 
    --(tmp->refcount); 
    if (tmp->refcount == 0) { 
     remove_from_global_collection_of_atoms(a); 
     free(atom->stringdata); 
     free(atom); 
    } 
} 
void refAtom(const atom *a) { 
    ++(((atom*) a)->refcount); 
} 

È stupido, perché un design migliore sarebbe quella di inoltrare-dichiarare la atom, per rendere i puntatori ad esso completamente opaco e fornire una funzione per accedere ai dati stringati. Ma C non richiede che tu incapsuli tutto, ti permette di restituire i puntatori a tipi completamente definiti e vuole supportare questo tipo di utilizzo const per presentare una vista di sola lettura di un oggetto che è "realmente" modificabile.

2

I cast di C indicano al compilatore che sai cosa stai facendo e che assicurerai che tutto funzioni alla fine. Se li usi senza capire esattamente cosa stai facendo, puoi metterti nei guai.

In questo caso, il compilatore è perfettamente in suo diritto per inserire i nella memoria di sola lettura, in modo che questo codice si arresti in modo anomalo durante l'esecuzione. In alternativa, potrebbe funzionare come hai visto. Lo standard specifica questo comportamento non definito, quindi letteralmente potrebbe accadere qualsiasi cosa.

C è stato originariamente progettato per scrivere Unix e fornisce deliberatamente al programmatore una grande libertà nella manipolazione dei dati, poiché nella scrittura del SO è spesso molto utile scrivere codice altamente specifico per l'implementazione che fa cose che non sarebbero sicure in qualsiasi altro contesto. Nel normale codice dell'applicazione, la fusione deve essere eseguita con cautela.

E non utilizzare i cast in stile C in C++. C++ ha una sua famiglia di cast che è facile da cercare nel codice e che spesso specifica di più ciò che il cast sta effettivamente facendo. In questo caso particolare, useresti const_cast, che mostra esattamente quello che stai facendo ed è facile da trovare.

2

Perché C sa che i programmatori sanno sempre cosa stanno facendo e fanno sempre la cosa giusta. È anche possibile lanciare le cose come:

void* ptr2 = (int(*)(void(*)(char*), int[])) ptr1; 
(*(int*)ptr2) = 3; 

e non sarà nemmeno lamentare affatto.

1

La dichiarazione const int i = 2; significa che il simbolo/variabile i ha un valore 2, e il valore non può essere modificato utilizzando i; ma non vi è alcuna garanzia che il valore non possa essere modificato con altri mezzi (come nell'esempio di OP).