Inkonsistenz bei der direkten Verwendung des Zeigers zu einem Array und der Adresse eines Arrays

8

Dieses Codebeispiel druckt das Array korrekt.

%Vor%

, während dieser zwei Müllwerte ausgibt.

%Vor%

Warum verhalten sich die beiden Codebeispiele anders?

    
gabber12 15.07.2013, 18:19
quelle

4 Antworten

9

Die Erklärung:

%Vor%

Erzeugt ein Array von zwei int mit den Werten 1 , 2 .
Angenommen, in einer Systemgröße von int ist 4 Bytes, dann sollte das Array b[] im Speicher etwa wie folgt gespeichert werden:

%Vor%

Im obigen Diagramm bedeuten Speicherzellen mit dem Wert ? Garbage-Werte und sind nicht zugewiesen (Speicher von 0xbf5c7884 ist für unser Array nicht reserviert). Die Werte 1 , 2 werden im Speicher in der Adresse 0xbf5c787c und 0xbf5c7880 gespeichert, die im Array b[] zugeordnet sind.

Anstatt Werte auszudrucken, drucken wir Speicheradressen, auf die Sie in Ihrem Code zugreifen, mit (c + i) und (&b + i) , dazu folgendes Programm:

%Vor%

Ausgaben:

%Vor%

Überprüfen Sie, ob dieser Code @ Codepade funktioniert Beachten Sie, dass (c + i) die korrekte Adresse der Zellen mit dem Wert 1 , 2 ausgibt, daher sind die Ausgaben in Ihrem ersten Code korrekt. Während (&b + i) einen Adresswert ausgibt, der nicht dem Array b[] zugeordnet ist (also außerhalb des Arrays b[] ) und der Zugriff auf diesen Speicher zur Laufzeit ein nicht definiertes Verhalten (nicht vorhersagbar) ergibt.

Tatsächlich gibt es einen Unterschied zwischen b und &b .

  • b ist ein Array und sein Typ ist int[2] , b zerfällt in int* als Adresse für das erste Element in den meisten Ausdrücken (lesen Sie: einige Ausnahmen wo Array-Name nicht in einen Zeiger auf das erste Element verfallen? ). Und b + 1 zeigt auf die nächsten int Elemente im Array (beachten Sie das Diagramm).

  • &b ist die Adresse des vollständigen Arrays und sein Typ ist int(*)[2] , (&b + 1) zeigt auf das nächste Array vom Typ int[2] , das in Ihrem Programm nicht zugeordnet ist (Hinweis im Diagramm, wo (&b + 1) Punkte).

Um weitere interessante Unterschiede zwischen b und &b zu erfahren, lesen Sie: Was? Gibt sizeof(&array) zurück?

Wenn Sie in der ersten Code-Schnipsel c = &b eingeben, weisen Sie die Adresse des Arrays int* zu (in unserem Beispiel 0xbf5c787c ). Mit GCC-Compiler wird diese Anweisung warnen: "Zuweisung von inkompatiblen Zeigertyp".
Da c ein Zeiger auf int ist, druckt *(c + i) eine Ganzzahl, die in der Adresse (c + i) gespeichert ist. Für i = 1 zeigt der Wert (c + 1) auf das zweite Element im Array (in unserem Beispiel bei 0xbf5c7880 ). Daher zeigt *(c + 1) korrekt 2 an.

In Bezug auf die Zuordnung int *c = &b; im ersten Code empfehle ich sehr, die Antwort von @ AndreyT unten zu lesen. Der richtige und einfache Weg, um mit Zeiger auf Array-Elemente zuzugreifen, ist wie folgt:

%Vor%

Wenn Sie in Ihrem zweiten Code i zu &b hinzufügen, wird es auf extern zugewiesenen Speicher zeigen und in der printf-Anweisung greifen Sie mit * -Deferenzierungs-Operator auf den Speicherzugriff zu und das Verhalten dieses Codes zur Laufzeit ist < a href="http://en.wikipedia.org/wiki/Undefined_behavior"> Nicht definiert . Das ist der Grund dafür, dass sich der zweite Code bei unterschiedlicher Ausführung anders verhält.

Ihr Code wird kompiliert, weil er syntaktisch korrekt ist, aber zur Laufzeit kann der Zugriff auf nicht zugeordneten Speicher vom Betriebssystemkern erkannt werden. Dies kann dazu führen, dass der OS-Kernel einen Signal-Core-Dump an den Prozess sendet, der die Exception verursacht hat (interessant zu beachten: wenn das Betriebssystem einen Speicherrechtsverletzung durch einen Prozess erkennt) - Ein ungültiger Zugriff auf gültigen Speicher ergibt: SIGSEGV und Zugriff auf eine ungültige Adresse : SIGBUS). Im Wertfall kann Ihr Programm ohne Fehler ausgeführt werden und Müll-Ergebnisse erzeugen.

Was den zweiten Code betrifft, wird die korrekte Methode zum Drucken eines Arrays unter Verwendung von 'pointer to array' wie folgt aussehen:

%Vor%

Ausgabe:

%Vor%

Überprüfen Sie @ Codepage . Punkt, der beachtet werden soll Klammer um *c wird benötigt, da der Vorrang von [] Operator höher ist als * Dereferenzierungsoperator (wohingegen, wenn Sie den Zeiger zu int verwenden, benötigen Sie keine Klammern wie im obigen Code).

    
Grijesh Chauhan 15.07.2013 20:16
quelle
5

Dies liegt an dem Zeigertyp, auf den die arithmetische Zeigeroperation ptr+i angewendet wird:

  • Im ersten Fall fügen Sie i zu einem Zeiger auf int hinzu, was der Indizierung eines Arrays entspricht. Da der Zeiger auf ein Array der gleiche wie der Zeiger auf sein erstes Element ist, funktioniert der Code.
  • Im zweiten Fall fügen Sie i einem Zeiger auf ein Array mit zwei int s hinzu. Daher werden Sie durch die Addition über den zugewiesenen Speicher hinaus versetzt, was zu undefiniertem Verhalten führt.

Hier ist eine kurze Illustration dieses Punktes:

%Vor%

Auf einem System mit 32-Bit int s werden Adressen gedruckt, die durch acht Bytes getrennt sind - die Größe von int[2] :

%Vor%     
dasblinkenlight 15.07.2013 18:23
quelle
1
%Vor%

Dies ist eigentlich nicht gültig. Sie brauchen eine Besetzung.

%Vor%

Die zwei Ausdrücke:

%Vor%

sind nicht gleich. Im ersten Ausdruck wird i zu einem int * hinzugefügt und im zweiten Ausdruck wird i zu einem int (*)[2] hinzugefügt. Mit int *c = (int *) &b; wird ein int (*)[2] in ein int * konvertiert. c + i zeigt auf das i -th int Element von c , aber &b+i zeigt auf das int [2] Element von b und verschiebt den Zeigerwert außerhalb des eigentlichen Array-Objekts.

    
ouah 15.07.2013 18:22
quelle
1

Der erste Code ist kaputt. Die Zuordnung

%Vor%

ist ungültig. Die rechte Seite hat den Typ int (*)[2] , während das Objekt links den Typ int * hat. Dies sind verschiedene, inkompatible Typen. Der Compiler hätte Sie über diesen Fehler informieren sollen, indem Sie eine Diagnosemeldung ausgegeben haben. Ignorieren Sie die von Compilern ausgegebenen Diagnosemeldungen nicht.

Der Code wurde trotz des obigen Problems vom Compiler aufgrund einer nicht standardmäßigen Compiler-Erweiterung akzeptiert, die es dem Compiler ermöglichte, den int (*)[2] -Zeiger in int * type zu konvertieren, wobei der numerische Wert des Zeigers erhalten blieb ( die physische Adresse). Sie haben also den int * -Zeiger auf den Anfang Ihres int [2] -Arrays gesetzt. Es ist nicht überraschend, dass der Zugriff auf den Speicher über diesen Zeiger Ihnen erlaubt, den Inhalt des Arrays zu sehen.

Ihr zweiter Code ist auch auf mehrere Arten aufgeteilt. Es leidet nicht unter dem ersten Problem, da Sie keine Konvertierung von &b -Wert (was wiederum den Typ int (*)[2] hat) zu etwas anderem erzwingen, sondern Zeigerarithmetik direkt auf &b anwenden. Gemäß den Regeln der Zeigerarithmetik erzeugt der Ausdruck &b + 1 einen Zeiger, der über das ursprüngliche b -Array hinausweist. Die Dereferenzierung eines solchen Zeigers ist illegal. % Co_de% erzeugt also bereits undefiniertes Verhalten. Darüber hinaus hat der Ausdruck *(&b + 1) den Typ *(&b + 1) , der in den Zeigertyp int [2] zerfällt. Also, in Ihrem zweiten Code versuchen Sie einen int * Wert mit int * Formatbezeichner zu drucken. Dies ist auch undefiniertes Verhalten. Die Manifestationen dieses undefinierten Verhaltens sehen Sie in Ihrem zweiten Beispiel.

Mit anderen Worten, Sie haben im ersten Code mehr Glück als im zweiten, weshalb die Ausgabe der ersten aussagekräftiger erscheint.

    
AnT 16.07.2013 16:06
quelle

Tags und Links