K & R geht nicht darüber hinweg, aber sie benutzen es. Ich habe versucht zu sehen, wie es funktionieren würde, indem ich ein Beispielprogramm geschrieben habe, aber es lief nicht so gut:
#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?
*n
wird ein seinint
, der 4 Bytes betragen sollte.*n
zeigt auf die lokale Variablec
inmain()
. Dies bedeutet, dass Sie den Wert von'c'
und die darauf folgenden drei Bytes im Speicher ausschreiben. (Meine Vermutung ist der Wert vond
.) Sie können dies überprüfen, indem Sie die Zahl in hexadezimaler Form ausschreiben - zwei der Ziffern sollten jedes Mal gleich sein.'5'
- Sie sollten denken, dass dies wie ein int aussieht, da es eine Zahl zu sein scheint, aber es ist nur ein Zeichen, das die Ziffer 5 darstellt.Antworten:
Wenn Sie über Zeiger nachdenken, ist es hilfreich, Diagramme zu zeichnen . Ein Zeiger ist ein Pfeil, der auf eine Adresse im Speicher zeigt, wobei eine Beschriftung den Typ des Werts angibt. Die Adresse gibt an, wo gesucht werden soll, und der Typ gibt an, was zu nehmen ist. Durch Umsetzen des Zeigers wird die Beschriftung des Pfeils geändert, jedoch nicht die Stelle, an der der Pfeil zeigt.
d
inmain
ist ein Zeigerc
vom Typchar
. Achar
ist ein Byte Speicher. Wennd
also dereferenziert wird, erhalten Sie den Wert in diesem einen Byte Speicher. In der folgenden Abbildung repräsentiert jede Zelle ein Byte.-+----+----+----+----+----+----+- | | c | | | | | -+----+----+----+----+----+----+- ^~~~ | char d
Wenn Sie
d
zuint*
besetzen, sagen Sie, dass diesd
wirklich auf einenint
Wert hinweist . Auf den meisten heutigen Systemenint
belegt ein 4 Bytes.-+----+----+----+----+----+----+- | | c | ?₁ | ?₂ | ?₃ | | -+----+----+----+----+----+----+- ^~~~~~~~~~~~~~~~~~~ | int (int*)d
Wenn Sie dereferenzieren
(int*)d
, erhalten Sie einen Wert, der aus diesen vier Speicherbytes bestimmt wird. Der Wert, den Sie erhalten, hängt davon ab, was in diesen Zellen markiert?
ist und wie einint
im Speicher dargestellt wird.Ein PC ist Little-Endian , was bedeutet, dass der Wert von a auf
int
diese Weise berechnet wird (vorausgesetzt, er umfasst 4 Bytes) :* ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴
. Wenn Sie also hexadezimal (printf("%x\n", *n)
) drucken , werden die letzten beiden Ziffern immer angezeigt35
(das ist der Wert des Zeichens'5'
) , während der Wert Müll ist .Einige andere Systeme sind Big-Endian-Systeme und ordnen die Bytes in die andere Richtung :
* ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃
. Auf diesen Systemen beginnt der Wert immer mit35
hexadezimalem Druck. Einige Systeme haben eine Größeint
, die sich von 4 Bytes unterscheidet. Einige wenige Systeme sindint
unterschiedlich angeordnet, 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 bei jedem Ausführen des Programms unterschiedlich ist oder dass er immer gleich ist, sich jedoch ändert, wenn Sie den Quellcode auch nur geringfügig ändern.
Auf einigen Systemen muss ein
int
Wert in einer Adresse gespeichert werden, die ein Vielfaches von 4 (oder 2 oder 8) ist. Dies wird als Ausrichtungsanforderung bezeichnet . Je nachdem, ob die Adresse vonc
richtig ausgerichtet ist oder nicht, kann das Programm abstürzen.Im Gegensatz zu Ihrem Programm geschieht Folgendes, wenn Sie einen
int
Wert haben und einen Zeiger darauf nehmen.int x = 42; int *p = &x;
-+----+----+----+----+----+----+- | | x | | -+----+----+----+----+----+----+- ^~~~~~~~~~~~~~~~~~~ | int p
Der Zeiger
p
zeigt auf einenint
Wert. Das Etikett auf dem Pfeil beschreibt korrekt, was sich in der Speicherzelle befindet, sodass beim Dereferenzieren keine Überraschungen auftreten.quelle
char *a = "abcd"; int *i = (int *)a; printf("%x\n", *i);
ausprobiert habe, ist die Ausgabe 64636261, aber ich denke, es sollte 61626364 sein. Bedeutet dies, dass der Speicher in diesem int-Block von hinten nach vorne gelesen wird?int
wird wahrscheinlich sowieso in einer einzigen Anweisung gelesen. Hier geht es darum, wie ein Block von 4 Bytes alsint
Wert interpretiert wird.int*
Zeiger war. Jeder Datenzeiger kann hinunsigned char*
und zurück geworfen werden, und ich denke, erunsigned char *
kann hinchar *
und zurück geworfen werden.char c = '5'
Ein
char
(1 Byte) wird auf dem Stapel an der Adresse zugewiesen0x12345678
.char *d = &c;
Sie erhalten die Adresse von
c
und speichern sie ind
, alsod = 0x12345678
.int *e = (int*)d;
Sie zwingen den Compiler anzunehmen, dass
0x12345678
er auf ein zeigtint
, aber ein int ist nicht nur ein Byte (sizeof(char) != sizeof(int)
). Es können 4 oder 8 Bytes sein, je nach Architektur oder sogar anderen Werten.Wenn Sie also den Wert des Zeigers drucken, wird die Ganzzahl berücksichtigt, indem das erste Byte (das war
c
) und andere aufeinanderfolgende Bytes, die sich auf dem Stapel befinden und nur Müll für Ihre Absicht sind, verwendet werden.quelle
d
, dh0x12345678
in Ihrem Beispiel.d
ist nicht groß genug, um zu halten0x12345678
Das Casting von Zeigern ist in C normalerweise ungültig. Es gibt mehrere Gründe:
Ausrichtung. Es ist möglich, dass der Zielzeigertyp aufgrund von Überlegungen zur Ausrichtung den Wert des Quellzeigertyps nicht darstellen kann. Wenn beispielsweise von
int *
Natur aus 4 Byte ausgerichtet wärenchar *
,int *
würde das Casting in die unteren Bits verlieren.Aliasing. Im Allgemeinen ist der Zugriff auf ein Objekt verboten, außer über einen Wert 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 (wenden Sie die Operatoren
*
oder->
auf ihn an oder übergeben Sie ihn an eine Funktion, die ihn dereferenziert).Die wichtigsten bemerkenswerten Fälle, in denen das Casting von Zeigern in Ordnung ist, sind:
Wenn der Zielzeigertyp auf den Zeichentyp zeigt. Zeiger auf Zeichentypen können garantiert jeden Zeiger auf einen beliebigen Typ darstellen und ihn auf Wunsch erfolgreich auf den ursprünglichen Typ zurückführen. Der Zeiger auf void (
void *
) ist genau das Gleiche wie ein Zeiger auf einen Zeichentyp, außer dass Sie ihn nicht dereferenzieren oder arithmetisch bearbeiten dürfen, und er konvertiert automatisch zu und von anderen Zeigertypen, ohne dass eine Umwandlung erforderlich ist, also Zeiger auf void sind für diesen Zweck normalerweise Zeigern auf Zeichentypen vorzuziehen.Wenn der Zielzeigertyp ein Zeiger auf den Strukturtyp ist, dessen Elemente genau mit den ursprünglichen Elementen des ursprünglich angegebenen Strukturtyps übereinstimmen. Dies ist nützlich für verschiedene objektorientierte Programmiertechniken in C.
Einige andere dunkle Fälle sind in Bezug auf die Sprachanforderungen technisch in Ordnung, aber problematisch und werden am besten vermieden.
quelle
malloc
und Sie Daten byteweise überfread
oder ähnlich darin speichern , solange die Offsets angemessen ausgerichtet sind (im Allgemeinen kann dies schwer zu bestimmen sein, aber es ist sicherlich wahr, wenn sie ein Vielfaches der Typgröße sind ) Es sollte konform sein, in den entsprechenden Zeigertyp zu konvertieren und auf die Daten als diesen Typ zuzugreifen. Wenn Sie jedoch mit einem Puffer arbeiten, dessen tatsächlicher Typchar[N]
oder etwas anderes ist , ist er nicht gültig.Ich vermute, Sie brauchen eine allgemeinere Antwort:
Es gibt keine Regeln für das Wirken von Zeigern in C! Mit der Sprache können Sie jeden Zeiger kommentarlos auf einen anderen Zeiger umwandeln.
Aber die Sache ist: Es gibt keine Datenkonvertierung oder was auch immer getan! Es liegt allein in Ihrer Verantwortung, dass das System die Daten nach der Umwandlung nicht falsch interpretiert - was im Allgemeinen der Fall ist und zu Laufzeitfehlern führt.
Wenn Sie also das Casting durchführen, liegt es ganz bei Ihnen, darauf zu achten, dass die Daten kompatibel sind, wenn Daten von einem gegossenen Zeiger verwendet werden!
C ist für die Leistung optimiert, daher fehlt die Laufzeitreflexivität von Zeigern / Referenzen. Aber das hat seinen Preis - Sie als Programmierer müssen sich besser darum kümmern, was Sie tun. Sie müssen selbst wissen, ob das, was Sie tun möchten, "legal" ist.
quelle
Sie haben einen Zeiger auf a
char
. Wie Ihr System weiß, befindet sich auf dieser Speicheradresse einchar
Wert für densizeof(char)
Speicherplatz. Wenn Sie es umwandelnint*
, arbeiten Sie mit Daten vonsizeof(int)
, sodass Sie Ihr Zeichen und etwas Speichermüll danach als Ganzzahl drucken.quelle