Was sind die Regeln für das Casting von Zeigern in C?

Lesezeit: 10 Minuten

Benutzeravatar von Theo Chronic
Theo Chronik

K&R geht nicht darüber hinweg, aber sie verwenden es. Ich habe versucht zu sehen, wie es funktionieren würde, indem ich ein Beispielprogramm geschrieben habe, aber es hat nicht so gut geklappt:

#include <stdio.h> 
int bleh (int *); 

int main(){
    char c="5"; 
    char *d = &c;

    bleh((int *)d); 
    return 0;  
}

int bleh(int *n){
    printf("%d bleh\n", *n); 
    return *n; 
}

Es wird kompiliert, aber meine print-Anweisung spuckt Müllvariablen aus (sie sind jedes Mal anders, wenn ich das Programm aufrufe). Irgendwelche Ideen?

  • int hat eine größere Größe als char, also liest es über das Leerzeichen von ‘5’ char hinaus. Versuchen Sie dasselbe mit einem kleineren Datentyp (int c, printf “%c”).

    – SheetJS

    23. Juni 2013 um 11:59 Uhr

  • Der Wert von *n wird ein sein intwas 4 Bytes sein sollte. *n zeigt auf die lokale Variable c in main(). Dies bedeutet, dass Sie den Wert von ausschreiben 'c' und welche drei Bytes ihm im Speicher folgen. (Meine Vermutung ist der Wert von d.) Sie können dies überprüfen, indem Sie die Zahl in Hex ausschreiben – zwei der Ziffern sollten jedes Mal gleich sein.

    – Millielch

    23. Juni 2013 um 12:00 Uhr

  • '5' — Sie denken vielleicht, dass dies wie ein Int aussieht, da es eine Zahl zu sein scheint, aber es ist nur ein Zeichen, das die Ziffer 5 darstellt.

    – mah

    23. Juni 2013 um 12:17 Uhr

  • Ich habe den gleichen Test auf meinem Computer (gcc, x86_64) durchgeführt und keine Kompilierungsfehler erhalten, und das Programm läuft jedes Mal einwandfrei (kein Müll). Aber ich habe nichts anders gemacht als beim OP. Seltsam.

    – Andi J

    20. Juni 2014 um 3:41 Uhr

  • Jeder, der diese Antwort liest, sollte sich die Antwort von R. unten ansehen

    – polynomial_donut

    13. August 2018 um 18:42 Uhr

Wenn Sie über Zeiger nachdenken, hilft es Diagramme zeichnen. Ein Zeiger ist ein Pfeil, der auf eine Adresse im Speicher zeigt, wobei eine Bezeichnung den Typ des Werts angibt. Die Adresse gibt an, wo gesucht werden muss, und der Typ gibt an, was zu nehmen ist. Das Bewegen des Mauszeigers ändert die Beschriftung auf dem Pfeil, aber nicht, wohin der Pfeil zeigt.

d in main ist ein Hinweis auf c was vom Typ ist char. EIN char ist ein Byte Speicher, also wann d dereferenziert wird, erhalten Sie den Wert in diesem einen Byte Speicher. Im folgenden Diagramm repräsentiert jede Zelle ein Byte.

-+----+----+----+----+----+----+-
 |    | c  |    |    |    |    | 
-+----+----+----+----+----+----+-
       ^~~~
       | char
       d

Wenn Sie werfen d zu int*das sagst du d deutet wirklich auf einen hin int Wert. Auf den meisten Systemen ist heute ein int belegt 4 Byte.

-+----+----+----+----+----+----+-
 |    | c  | ?₁ | ?₂ | ?₃ |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       (int*)d

Wenn Sie dereferenzieren (int*)derhalten Sie einen Wert, der aus diesen vier Speicherbytes ermittelt wird. Der Wert, den Sie erhalten, hängt davon ab, was in diesen Zellen markiert ist ?und wie ein int ist im Gedächtnis vertreten.

Ein PC ist Little-Endianwas bedeutet, dass der Wert von an int wird so berechnet (unter der Annahme, dass es 4 Bytes umfasst):
* ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴. Sie werden also sehen, dass, während der Wert Müll ist, wenn Sie in hexadezimal (printf("%x\n", *n)), sind die letzten beiden Ziffern immer 35 (Das ist der Wert des Charakters '5').

Einige andere Systeme sind Big-Endian und ordnen die Bytes in die andere Richtung: * ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃. Auf diesen Systemen finden Sie diesen Wert immer beginnt mit 35 wenn hexadezimal gedruckt. Einige Systeme haben eine Größe von int das ist anders als 4 Bytes. Ein paar seltene Systeme arrangieren int auf unterschiedliche Weise, aber es ist äußerst unwahrscheinlich, dass Sie ihnen begegnen.

Abhängig von Ihrem Compiler und Betriebssystem stellen Sie möglicherweise fest, dass der Wert jedes Mal anders ist, wenn Sie das Programm ausführen, oder dass er immer gleich ist, sich aber ändert, wenn Sie auch nur geringfügige Änderungen am Quellcode vornehmen.

Auf einigen Systemen, an int Der Wert muss in einer Adresse gespeichert werden, die ein Vielfaches von 4 (oder 2 oder 8) ist. Das nennt man ein Ausrichtung Erfordernis. Je nachdem, ob die Adresse von c richtig ausgerichtet ist oder nicht, kann das Programm abstürzen.

Im Gegensatz zu Ihrem Programm passiert Folgendes, wenn Sie eine haben int Wert und nehmen Sie einen Zeiger darauf.

int x = 42;
int *p = &x;
-+----+----+----+----+----+----+-
 |    |         x         |    | 
-+----+----+----+----+----+----+-
       ^~~~~~~~~~~~~~~~~~~
       | int
       p

Der Zeiger p weist auf ein int Wert. Die Beschriftung auf dem Pfeil beschreibt korrekt, was sich in der Speicherzelle befindet, sodass es beim Dereferenzieren keine Überraschungen gibt.

  • Gute Beschreibung. Ich möchte darauf hinweisen/diskutieren, dass es auf den meisten Computern zutreffen kann, dass int ein 32-Bit-Wert ist, aber für andere Embedded-Ingenieure ist es int normalerweise 16-Bit und es zeigt, wie nützlich und wahrscheinlich wichtig es ist, uint16_t, uint32_t, int32_t usw. usw. zu verwenden. 🙂

    – DiBosco

    1. Februar 2017 um 10:28 Uhr


  • “… die letzten beiden Ziffern sind immer 35 (das ist der Wert des Zeichens ‘5’).” Wieso den?

    – Kenny Worden

    9. April 2017 um 22:59 Uhr


  • Hallo Gilles, als ich den Code hier ausprobiert habe char *a = "abcd"; int *i = (int *)a; printf("%x\n", *i); Die Ausgabe ist 64636261, aber ich denke, es sollte 61626364 sein. Bedeutet dies, dass der Speicher in diesem int-Block von hinten nach vorne gelesen wird?

    – Sommersonne

    29. Juni 2018 um 6:05 Uhr

  • @SummerSun Warum sollte es deiner Meinung nach 61626364 sein? Wenn Sie eine Little-Endian-Maschine haben (alle PCs sind Little-Endian), wäre es 64636261. Dies hat nichts mit der Reihenfolge zu tun, in der der Speicher gelesen wird. Ein int wird wahrscheinlich sowieso in einer einzigen Anweisung gelesen. Hier geht es darum, wie ein Block von 4 Bytes als interpretiert wird int Wert.

    – Gilles ‘SO- hör auf, böse zu sein’

    29. Juni 2018 um 6:21 Uhr

  • @Malcolm Es ist ein undefiniertes Verhalten. Das Dereferenzieren des Ergebnisses der Umwandlung ist UB (es könnte beispielsweise nicht richtig ausgerichtet sein), und selbst das bloße Konstruieren eines Zeigers ist normalerweise UB, wenn das Dereferenzieren UB wäre (ich denke, die einzigen Ausnahmen sind Funktionszeiger und Zeiger auf das Ende von eine Anordnung). Es gibt einen Fall, in dem das Verhalten definiert ist, nämlich wenn der Zeiger ursprünglich ein war int* Zeiger; jeder Datenzeiger kann gecastet werden unsigned char* und zurück, und ich denke unsigned char * kann gecastet werden char * und zurück.

    – Gilles ‘SO- hör auf, böse zu sein’

    15. September 2019 um 21:43 Uhr

Jacks Benutzeravatar
Jack

char c="5"

EIN char (1 Byte) wird auf dem Stapel bei der Adresse zugewiesen 0x12345678.

char *d = &c;

Sie erhalten die Adresse von c und speichere es darin dAlso d = 0x12345678.

int *e = (int*)d;

Sie zwingen den Compiler, dies anzunehmen 0x12345678 weist auf ein intaber ein int ist nicht nur ein Byte (sizeof(char) != sizeof(int)). Je nach Architektur können es 4 oder 8 Bytes oder auch andere Werte sein.

Wenn Sie also den Wert des Zeigers drucken, wird die Ganzzahl berücksichtigt, indem das erste Byte genommen wird (das war c) und andere aufeinanderfolgende Bytes, die sich auf dem Stapel befinden und für Ihre Absicht nur Müll sind.

  • Andere aufeinanderfolgende Bytes sind kein Müll, sondern der Wert von ddh 0x12345678 in deinem beispiel.

    – Kane

    23. Juni 2013 um 12:07 Uhr

  • d ist nicht groß genug, um zu halten 0x12345678

    – Eine Person

    1. Januar 2014 um 2:33 Uhr


  • @APerson Warum ist das so?

    – yyny

    14. Januar 2018 um 22:24 Uhr

  • Zeichen c[] = “5”; verkohlen d = c; int *e = (int)d; printf(“%p \n”, e);

    – Marsmensch2049

    14. September 2018 um 15:13 Uhr

  • Dies ist in der Tat UB: wiki.sei.cmu.edu/confluence/display/c/…

    – BärAqua

    3. Oktober 2019 um 17:56 Uhr

Casting-Zeiger sind in C normalerweise ungültig. Dafür gibt es mehrere Gründe:

  1. Ausrichtung. Es ist möglich, dass der Zielzeigertyp aufgrund von Ausrichtungsüberlegungen nicht in der Lage ist, den Wert des Quellzeigertyps darzustellen. Zum Beispiel, wenn int * waren von Natur aus 4-Byte-ausgerichtet, Casting char * zu int * würde die unteren Bits verlieren.

  2. Aliasing. Im Allgemeinen ist es verboten, auf ein Objekt zuzugreifen, außer über einen lvalue des richtigen Typs für das Objekt. Es gibt einige Ausnahmen, aber wenn Sie sie nicht sehr gut verstehen, möchten Sie es nicht tun. Beachten Sie, dass Aliasing nur dann ein Problem darstellt, wenn Sie den Zeiger tatsächlich dereferenzieren (anwenden der * oder -> Operatoren oder übergeben Sie es an eine Funktion, die es dereferenziert).

Die wichtigsten bemerkenswerten Fälle, in denen das Casting von Zeigern in Ordnung ist, sind:

  1. Wenn der Zielzeigertyp auf einen Zeichentyp zeigt. Zeiger auf Zeichentypen sind garantiert in der Lage, jeden Zeiger auf jeden Typ darzustellen und ihn bei Bedarf erfolgreich auf den ursprünglichen Typ zurückzusetzen. Zeiger auf ungültig (void *) ist genau dasselbe wie ein Zeiger auf einen Zeichentyp, außer dass Sie ihn nicht dereferenzieren oder arithmetisch damit arbeiten dürfen, und er konvertiert automatisch in und von anderen Zeigertypen, ohne dass eine Umwandlung erforderlich ist, daher sind Zeiger auf void normalerweise vorzuziehen über Zeiger auf Zeichentypen für diesen Zweck.

  2. Wenn der Zielzeigertyp ein Zeiger auf einen Strukturtyp ist, dessen Mitglieder genau mit den Anfangsmitgliedern des ursprünglich gezeigten Strukturtyps übereinstimmen. Dies ist nützlich für verschiedene objektorientierte Programmiertechniken in C.

Einige andere obskure Fälle sind im Hinblick auf die Sprachanforderungen technisch in Ordnung, aber problematisch und sollten am besten vermieden werden.

  • Können Sie mit diesen obskuren Fällen auf ein offizielles Dokument verlinken?

    – Erich

    5. April 2017 um 14:13 Uhr

  • Ich habe an einigen Stellen Code gesehen, der ein char* nimmt und es in einen anderen Zeiger umwandelt, sagen wir int. Zum Beispiel Streaming von RGB-Werten von einer Kamera oder Bytes aus dem Netzwerk. Bedeutet Ihre Referenz, dass dieser Code ungültig ist? Reicht das Ausrichten der Daten aus, um den Code korrekt zu machen, oder sind unsere gängigen Compiler nur nachsichtig mit dieser Verwendung?

    – Eva Benn

    14. Dezember 2017 um 11:06 Uhr


  • @EvanBenn: Möglicherweise. Wenn der Puffer erhalten wird durch mallocund Sie speichern Daten byteweise über fread oder ähnliches, dann sollte es, solange die Offsets geeignet ausgerichtet sind (im Allgemeinen kann dies schwer zu bestimmen sein, aber es ist sicherlich wahr, wenn es sich um ein Vielfaches der Typgröße handelt), in den entsprechenden Zeigertyp konvertiert und darauf zugegriffen werden Daten als diesen Typ. Wenn Sie jedoch mit einem Puffer arbeiten, dessen tatsächlicher Typ ist char[N] oder so, es ist nicht gültig.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    14. Dezember 2017 um 17:26 Uhr

Ich vermute, Sie brauchen eine allgemeinere Antwort:

Es gibt keine Regeln zum Casten von Zeigern in C! Die Sprache ermöglicht es Ihnen, jeden Zeiger ohne Kommentar auf jeden anderen Zeiger umzuwandeln.

Aber die Sache ist: Es wird keine Datenkonvertierung oder was auch immer gemacht! Es liegt in Ihrer alleinigen Verantwortung, dass das System die Daten nach dem Casting nicht falsch interpretiert – was in der Regel der Fall wäre und zu Laufzeitfehlern führen würde.

Beim Casting liegt es also ganz bei Ihnen, darauf zu achten, dass die Daten kompatibel sind, wenn Daten von einem gecasteten Zeiger verwendet werden!

C ist auf Leistung optimiert, daher fehlt es an Laufzeitreflexivität von Zeigern/Referenzen. Aber das hat seinen Preis – Sie als Programmierer müssen besser aufpassen, was Sie tun. Sie müssen selbst wissen, ob das, was Sie tun wollen, “legal” ist

Sie haben einen Zeiger auf a char. Wie Ihr System weiß, gibt es an dieser Speicheradresse a char Wert an sizeof(char) Platz. Wenn Sie es aufwerfen int*Sie werden mit Daten von arbeiten sizeof(int)also werden Sie Ihr Zeichen und etwas Speichermüll danach als Ganzzahl drucken.

1419370cookie-checkWas sind die Regeln für das Casting von Zeigern in C?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy