it-swarm.com.de

Welche Regeln gelten für das Umsetzen von Zeigern in C?

K & R geht nicht darüber nach, aber sie benutzen es. Ich habe versucht zu sehen, wie es funktioniert, 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 gibt Müllvariablen aus (sie sind bei jedem Aufruf des Programms anders). Irgendwelche Ideen?

60
Theo Chronic

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 Sie suchen müssen, und der Typ gibt an, was Sie mitnehmen müssen. Durch das Umsetzen des Zeigers wird die Beschriftung des Pfeils geändert, nicht jedoch die Position des Pfeils.

d in main ist ein Zeiger auf c vom Typ char. Ein char ist ein Byte Speicher. Wenn also 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 d in int* Umwandeln, sagen Sie, dass d wirklich auf einen int Wert verweist. Auf den meisten heutigen Systemen belegt ein int 4 Bytes.

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

Wenn Sie (int*)d Dereferenzieren, erhalten Sie einen Wert, der aus diesen vier Bytes Speicher bestimmt wird. Der Wert, den Sie erhalten, hängt davon ab, was in diesen Zellen mit ? Markiert ist und wie ein int im Speicher dargestellt wird.

Ein PC ist Little-Endian , was bedeutet, dass der Wert eines int auf diese Weise berechnet wird (unter der Annahme, dass er 4 Bytes umfasst): * ((int*)d) == c + ?₁ * 2⁸ + ?₂ * 2¹⁶ + ?₃ * 2²⁴. Sie werden also sehen, dass, während der Wert Müll ist, wenn Sie hexadezimal (printf("%x\n", *n)) drucken, die letzten beiden Ziffern immer 35 Sind (das ist der Wert des Zeichens '5').

Einige andere Systeme sind Big-Endian-Systeme und ordnen die Bytes in die andere Richtung an: * ((int*)d) == c * 2²⁴ + ?₁ * 2¹⁶ + ?₂ * 2⁸ + ?₃. Auf diesen Systemen würden Sie feststellen, dass der Wert immer beginnt mit 35 In hexadezimaler Schreibweise ausgegeben wird. Einige Systeme haben eine Größe von int, die sich von 4 Byte unterscheidet. Einige wenige Systeme ordnen int auf unterschiedliche Weise an, aber es ist äußerst unwahrscheinlich, dass Sie auf sie stoßen.

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 geringfügige Änderungen am Quellcode vornehmen.

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 Ausrichtung Anforderung bezeichnet. Abhängig davon, ob die Adresse von c 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 setzen.

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

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

115
Gilles
char c = '5'

Ein char (1 Byte) wird auf dem Stack an der Adresse 0x12345678 Zugewiesen.

char *d = &c;

Sie erhalten die Adresse von c und speichern sie in d, also d = 0x12345678.

int *e = (int*)d;

Sie erzwingen, dass der Compiler annimmt, dass 0x12345678 Auf ein int verweist, aber ein int nicht nur ein Byte ist (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 ausgeben, wird die Ganzzahl berücksichtigt, indem das erste Byte (das war c) und andere aufeinanderfolgende Bytes verwendet werden, die sich auf dem Stapel befinden und nur Müll für Ihre Absicht sind.

36
Jack

Das Umsetzen von Zeigern ist in der Regel in C ungültig. Es gibt mehrere Gründe:

  1. Ausrichtung. Aus Gründen der Ausrichtung kann der Zielzeigertyp möglicherweise nicht den Wert des Quellzeigertyps darstellen. Wenn beispielsweise int * Inhärent 4-Byte-ausgerichtet wäre, würde das Umwandeln von char * In int * Die unteren Bits verlieren.

  2. Aliasing. Im Allgemeinen ist es verboten, auf ein Objekt zuzugreifen, 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 -> Darauf an oder übergeben Sie ihn an eine Funktion, die ihn dereferenziert).

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

  1. Wenn der Zielzeigertyp auf den Zeichentyp zeigt. Zeiger auf Zeichentypen können garantiert jeden Zeiger auf einen beliebigen Typ darstellen und auf Wunsch erfolgreich auf den ursprünglichen Typ zurückführen. Zeiger auf ungültig (void *) Ist genau das Gleiche wie ein Zeiger auf einen Zeichentyp, mit der Ausnahme, dass Sie ihn nicht dereferenzieren oder arithmetisch bearbeiten dürfen. Er wird automatisch zu und von anderen Zeigertypen konvertiert, ohne dass dies erforderlich ist Eine Besetzung, sodass Zeiger auf ungültig gegenüber Zeigern auf Charaktertypen für diesen Zweck vorzuziehen sind.

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

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

12
R..

Sie haben einen Zeiger auf ein char. Wie Ihr System weiß, gibt es auf dieser Speicheradresse einen char Wert in sizeof(char) Leerzeichen. Wenn Sie es auf int* Hochwandeln, arbeiten Sie mit Daten von sizeof(int), sodass Sie Ihr Zeichen und etwas Speicherabfall danach als Ganzzahl ausgeben.

2
gkovacs90

Ich vermute, Sie brauchen eine allgemeinere Antwort:

In C gibt es keine Regeln für das Wirken von Zeigern! Mit der Sprache können Sie jeden Zeiger ohne Kommentar auf einen anderen Zeiger setzen.

Aber die Sache ist: Es erfolgt keine Datenkonvertierung oder was auch immer! Es liegt ausschließlich 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 einen Cast durchführen, müssen Sie darauf achten, dass die Daten kompatibel sind, wenn Daten von einem Cast-Zeiger verwendet werden!

C ist für die Leistung optimiert, so dass es keine Laufzeitreflexivität von Zeigern/Referenzen gibt. Aber das hat einen 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.

2
Ole Dittmann

Der Garbage-Wert ist eigentlich darauf zurückzuführen, dass Sie haben die Funktion bleh () vor ihrer Deklaration aufgerufen.

In c ++ erhalten Sie einen Kompilierungsfehler, aber in c geht der Compiler davon aus dass der Rückgabetyp der Funktion int ist, während Ihre Funktion zurückgibt a Zeiger auf Ganzzahl

Weitere Informationen finden Sie hier: http://www.geeksforgeeks.org/g-fact-95/

1
Saket