2011-01-06 5 views
5

Ich war überrascht, als das folgende Programm nicht abstürzte.Zugriff auf Strukturelemente mit Hilfe von Zeigern

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

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

Gemäß meinem Verständnis der -> Operator hat eine höhere Priorität über & Betreiber. Ich erwartete also, dass das Programm bei der folgenden Anweisung abstürzt, wenn wir versuchen, den NULL-Zeiger tr zu dereferenzieren.

char *d = &ptr->b; 

Aber die Aussage &ptr->b ergibt eine gültige Adresse. Könnte jemand bitte erklären, wo ich falsch liege?

+0

Das ist irgendwie ähnlich wie das 'offsetof' Makro. – ruslik

Antwort

2

&ptr->b == sizeof(int), bedeutet, dass der Versatz von b innerhalb _x nach _x.a*((x*)0) an die Adresse relativ (die vom Typ int ist). Der Offset von 4 (typisch für 32-Bit-Architektur) wird im Zeiger d gespeichert. Sie müssen auf d zugreifen, um einen Segeldruck zu erhalten.

4

Der Grund dafür, dass Ihr Code nicht abstürzt, ist, dass Sie den Zeiger nicht wirklich dereferenziert haben. Beachten Sie, dass der Ausdruck

&ptr->b 

tatsächlich versucht nicht, den Inhalt von ptr oder ptr->b geladen. Stattdessen speichert es nur die Adresse des Speicherorts. Am Ende erhalten Sie einen Zeiger auf das b Feld des Objekts, auf das ptr zeigt. Dies ist ein paar Bytes nach der Adresse 0, so dass die Dereferenzierung des soeben erstellten Zeigers einen Segfault verursacht.

2

Für die Berechnung einer Adresse ist kein Speicherzugriff erforderlich. &ptr->b bedeutet "gib mir die Adresse des b Feldes der Struktur, auf die ptr zeigt." Dies erfordert nicht, zu prüfen, was auch immer an diesem Speicherort gespeichert sein mag.

Es kann hilfreich sein, über die Indexierung eines Arrays anstelle einer Struktur nachzudenken. C definiert ptr[5] als äquivalent zu *(ptr + 5), was bedeutet, dass &(ptr[5]) das gleiche wie &(*(ptr + 5)) ist. Jetzt ist es einfach zu sehen, dass die & und die * "ausgleichen" und Sie mit (ptr + 5) verlassen, die nur ein Zeiger-Inkrement und nicht eine Last aus dem Speicher umfasst.

C macht dies leicht bewölkt, da es lvalues ​​von rvalues ​​unterscheidet. Das heißt, ein Ausdruck, der sich auf Speicher bezieht, wird auf der linken Seite eines Ausdrucks anders behandelt als auf der rechten Seite. Bei einer Anweisung wie x = y; lädt ein C-Compiler einen Wert von der Adresse y und speichert ihn unter der Adresse x. Dies ist die Unterscheidung: y wird implizit dereferenziert, aber x ist nicht.

5

Ihre Erwartungen waren unbegründet. C-Programme "stürzen" nicht notwendigerweise ab, wenn Sie Nullzeiger dereferenzieren. C-Programme zeigen so undefined Verhalten, wenn Sie versuchen, so etwas zu tun. Undefiniertes Verhalten kann sich auf viele verschiedene Arten manifestieren. Dies kann zu einem Absturz führen. Oder es kann etwas produzieren, das einem "arbeitenden" Programm ähnelt. Letzteres ist anscheinend in Ihrem Fall passiert.

Aber in jedem Fall ist das Verhalten Ihres Programms undefiniert.Und nein, es produziert keine "gültige Adresse", wie Sie fälschlicherweise zu glauben scheinen. Eine numerische Adresse, die einem Ort im Speicher entspricht, an dem kein Objekt existiert, ist nicht gültig (natürlich mit Ausnahme des Null-Pointer-Wertes).

Verwandte Themen