2011-01-06 5 views
5

Sono rimasto sorpreso quando il seguente programma non si è bloccato.Accesso agli elementi della struttura utilizzando i puntatori

typedef struct _x { 
    int a; 
    char b; 
    int c; 
} x; 

main() { 
    x *ptr = 0; 
    char *d = &ptr->b; 
} 

Come per la mia comprensione dell'operatore -> ha una maggiore precedenza su & operatore. Quindi mi aspettavo che il programma si arrestasse in modo anomalo alla seguente istruzione quando provassimo a dereferenziare il puntatore NULL tr.

char *d = &ptr->b; 

Tuttavia, la dichiarazione &ptr->b restituisce un indirizzo valido. Qualcuno potrebbe spiegare dove ho sbagliato?

+0

Questo è in qualche modo simile alla macro 'offsetof'. – ruslik

risposta

2

&ptr->b == sizeof(int), significa che l'offset di b entro _x dopo _x.a (che è di tipo int) rispetto all'indirizzo *((x*)0). L'offset di 4 (tipico per architettura a 32 bit) viene salvato all'interno del puntatore d. Devi accedere a d per ottenere un errore di seg.

4

Il motivo per cui il codice non si arresta in modo anomalo è che non si è effettivamente dereferenziato il puntatore. Si noti che l'espressione

&ptr->b 

in realtà non provare a caricare i contenuti di ptr o ptr->b. Invece, memorizza semplicemente l'indirizzo di dove si trova in memoria. Quello che finirai per ottenere è un puntatore a dove dovrebbe essere il campo b dell'oggetto puntato da ptr. Questo sarà qualche byte dopo l'indirizzo 0, quindi il dereferenziamento del puntatore appena creato causerà un segfault.

2

L'elaborazione di un indirizzo non richiede l'accesso alla memoria. &ptr->b significa "dammi l'indirizzo del campo b della struttura puntata da ptr". Ciò non richiede la visualizzazione di ciò che può essere memorizzato in quella posizione di memoria.

Potrebbe essere utile pensare all'indicizzazione di una matrice anziché di una struttura. C definisce ptr[5] come equivalente a *(ptr + 5), il che significa che &(ptr[5]) corrisponde a &(*(ptr + 5)). Ora è facile vedere che lo & e lo * "cancellano" e ti lasciano con (ptr + 5), che implica solo un incremento del puntatore e non un carico dalla memoria.

C rende leggermente nuvoloso perché distingue lvalue da rvalue. Cioè, un'espressione che si riferisce alla memoria viene trattata in modo diverso sul lato sinistro di un'espressione di quanto non sia sulla destra. Data una dichiarazione come x = y;, un compilatore C caricherà un valore dall'indirizzo di e lo memorizzerà nell'indirizzo di x. Questa è la distinzione: è implicitamente dereferenziato, ma non lo è x.

5

Le vostre aspettative erano infondate. I programmi C non si "bloccano" necessariamente quando si annullano i puntatori nulli. I programmi C mostrano il cosiddetto comportamento non definito quando si tenta di fare qualcosa del genere. Il comportamento indefinito può manifestarsi in molti modi diversi. Può causare un crash. Oppure può produrre qualcosa che assomiglia anche a un programma "funzionante". Quest'ultimo è ciò che apparentemente è accaduto nel tuo caso.

In ogni caso, il comportamento del programma non è definito.E no, non produce un "indirizzo valido" come credete erroneamente. Un indirizzo numerico che corrisponde a una posizione nella memoria in cui non esiste alcun oggetto non è valido (ad eccezione del valore del puntatore nullo, ovviamente).