2011-01-08 6 views
10

In C können Sie beide einfachen Datentypen wie int, float und Zeiger auf diese werfen.In C, wenn ich einen Zeiger werfen und dereferenzieren, ist es wichtig, welche ich zuerst mache?

Nun würde ich angenommen haben, dass, wenn Sie von einem Zeiger zu einem Typ auf den Wert eines anderen Typs (z. B. von *float zu int) konvertieren möchten, die Reihenfolge der Casting und Dereferenzierung spielt keine Rolle. I.e. das für eine Variable float* pf, haben Sie (int) *pf == *((int*) pf). Ähnlich wie Kommutativität in Mathematik ...

Dies scheint jedoch nicht der Fall zu sein. Ich schrieb ein Testprogramm:

#include <stdio.h> 
int main(int argc, char *argv[]){ 
    float f = 3.3; 
    float* pf = &f; 
    int i1 = (int) (*pf); 
    int i2 = *((int*) pf); 
    printf("1: %d, 2: %d\n", i1, i2); 
    return 0; 
} 

und auf meinem System die Ausgabe

1: 3, 2: 1079194419 

So den Zeigers Gießen scheint anders zu arbeiten aus dem Wert Gießen.

Warum ist das? Warum macht die zweite Version nicht das was ich denke?

Und ist das plattformabhängig, oder rufe ich irgendwie undefiniertes Verhalten auf?

Antwort

11

Im Folgenden wird der Float bei pf abgerufen und in eine Ganzzahl konvertiert. Das Casting hier ist eine Anfrage, den float in eine ganze Zahl zu konvertieren. Der Compiler erzeugt Code, um den Gleitkommawert in einen ganzzahligen Wert zu konvertieren. (Umwandlung von Gleitkommazahlen zu ganzen Zahlen ist eine „normale“ Sache zu tun.)

int i1 = (int) (*pf); 

Folgendes sagt FORCE zunächst die Compiler pf Punkte auf eine ganze Zahl zu denken (und ignorieren die Tatsache, dass pf ist ein Zeiger auf einen Schwimmer) und dann den ganzzahligen Wert erhalten (aber es ist kein ganzzahliger Wert). Das ist eine seltsame und gefährliche Sache. Das Casting disabled in diesem Fall die richtige Umwandlung. Der Compiler führt eine einfache Kopie der Bits im Speicher durch (erzeugt Papierkorb). (Und es könnte Speicherausrichtungsprobleme auch geben!)

int i2 = *((int*) pf); 

In der zweiten Anweisung "konvertieren" Sie Zeiger nicht. Sie sagen dem Compiler, worauf der Speicher verweist (in diesem Beispiel ist dies falsch).

Diese beiden Aussagen machen sehr verschiedene Dinge!

Denken Sie daran, dass c manchmal die gleiche Syntax verwendet, um verschiedene Operationen zu beschreiben.

=============

beachten, dass der Standard-Fließkommatyp in C doppelt so groß ist (die mathematische Bibliothek verwendet typischerweise Doppel Argumente).

1

Ein Int wird nicht im Speicher wie ein Float dargestellt. Was Sie sehen, ist der Compiler, der denkt, dass der Zeiger zu einem int ist, und er betrachtet die 32 Bits des Gedächtnisses und denkt, dass es ein int finden wird, wenn es tatsächlich einen undefinierten Teil des Floats findet, der zu sehr herauskommt große Zahl.

4

Wenn Sie zuerst dereferenzieren und später zu int umwandeln, erhalten Sie das übliche (abgeschnittene) Verhalten von Umwandlungen von float nach int. Wenn Sie den Zeiger zuerst auf int und dann auf die Dereferenz stellen, wird das Verhalten nicht vom Standard definiert. Normalerweise manifestiert es sich bei der Interpretation des Speichers, der den Gleitkomma als int enthält. Siehe http://en.wikipedia.org/wiki/IEEE_754-2008 für wie das funktioniert.

+3

Normalerweise manifestiert es sich als eine "strikte Aliasing" Verletzung, die zum Lesen der falschen Daten oder gar keine Daten, zumindest auf modernen gcc führt. –

+1

Es zeigt sich nicht in, es ist eine Verletzung, und ja, GCC warnt Sie normalerweise davor (es fängt nicht alle Fälle, afaik). Ich sagte, es ist nicht definiert, ich habe nur erklärt, was in seinem Fall passiert - Sie können das leicht überprüfen, z. mit einem kleinen Ruby Snippet '[3.3] .pack (" f "). auspacken (" I ") => [1079194419]' – etarion

+0

Nur zur Information, mein Beispielprogramm oben kompiliert ohne Warnungen unter '-Wall', also ja, Gcc fängt nicht alle Fälle ;-). – sleske

3

Natürlich tut es! Casting teilt dem Compiler mit, wie der Speicherbereich zu betrachten ist. Wenn Sie dann auf den Speicher zugreifen, versucht er, die Daten dort zu interpretieren, je nachdem, wie Sie darauf hingewiesen haben. Es ist einfacher, es anhand des folgenden Beispiels zu verstehen.

int main() 
{ 
    char A[] = {0, 0, 0, 1 }; 
    int p = *((int*)A); 
    int i = (int)*A; 
    printf("%d %d\n", i, p); 
    return 0; 
} 

Die Ausgabe auf einer 32-Bit-Little-Endian-Maschine 0 16777216 sein. Das liegt daran, dass (int*)A den Compiler anweist, A als einen Zeiger auf Integer zu behandeln, und daher, wenn Sie A dereferenzieren, betrachtet es die 4 Bytes beginnend mit A (as sizeof(int) == 4). Nach Berücksichtigung der Bytereihenfolge, der Inhalt des 4 Bytes 16777216 Auf der anderen Seite ausgewertet dereferenziert *A A 0 zu bekommen und (int)*A wirft, dass 0.

+0

Danke für das Beispiel. BTW, ich glaube, es gibt nicht einmal eine Garantie, dass "Int" 32 Bit ist, es könnte 16 Bit sein. Mit einem 16bit int sollte Ihr Beispiel 256 (= 2^8) auf Little-Endian-Architekturen ergeben, oder? – sleske

+0

Hatte 32-Bit-Ganzzahl angenommen. Wird die Antwort aktualisieren, um dies explizit zu machen. Wenn int 16 Bits (2 Bytes) ist, werden nur zwei Bytes (A [0], A [1]) verwendet. Als A [0] = A [1] = 0 wird der ganzzahlige Wert 0 sein. – 341008

2

Cast zu bekommen, dann dereferenzieren Mittel „so tun, ich bin auf diese andere Sache zeigen und dann die andere Sache bekommen, auf die angeblich hingewiesen wird ".

Dereferenzierung, dann bedeutet Cast "holen Sie das Ding, auf das ich tatsächlich zeige, und wandeln Sie es dann nach den üblichen Regeln in dieses andere Ding um".

"Die üblichen Regeln" können die Bits ändern, um einen Wert eines anderen Typs zu erhalten, der logisch den gleichen Wert darstellt. Wenn du vorgibst, auf etwas zu zeigen, auf das du nicht wirklich zeigst, kann es nicht.

0

Gemäß 6.5/7 des Standards, grob gesprochen gespeicherter Wert muss mit dem effektiven Typ oder einem Zeichentyp kompatibel sein. Also, ich denke, die Aussage int i2 = *((int*) pf) ist nicht gut definiert.

+0

Die Aussage ist fast sicher ** falsch **. – davep

Verwandte Themen