2010-07-14 4 views
43

Ich verwendete den folgenden Codeabschnitt, um Daten aus Dateien als Teil eines größeren Programms zu lesen.Dereferenzieren von Typ-Punzed Pointer bricht strenge Aliasing-Regeln

double data_read(FILE *stream,int code) { 
     char data[8]; 
     switch(code) { 
     case 0x08: 
      return (unsigned char)fgetc(stream); 
     case 0x09: 
      return (signed char)fgetc(stream); 
     case 0x0b: 
      data[1] = fgetc(stream); 
      data[0] = fgetc(stream); 
      return *(short*)data; 
     case 0x0c: 
      for(int i=3;i>=0;i--) 
       data[i] = fgetc(stream); 
      return *(int*)data; 
     case 0x0d: 
      for(int i=3;i>=0;i--) 
       data[i] = fgetc(stream); 
      return *(float*)data; 
     case 0x0e: 
      for(int i=7;i>=0;i--) 
       data[i] = fgetc(stream); 
      return *(double*)data; 
     } 
     die("data read failed"); 
     return 1; 
    } 

Jetzt bin ich sagte -O2 zu verwenden, und ich bekomme folgende gcc Warnung: warning: dereferencing type-punned pointer will break strict-aliasing rules

googleing Ich fand zwei orthogonal Antworten:

vs

Am Ende will ich nicht die Warnungen ignorieren. Was würden Sie empfehlen?

[Update] Ich ersetzte das Spielzeug Beispiel mit der realen Funktion.

+0

Ihre Funktion eine doppelte zurückkehrt, aber Sie Wirf deine Rückkehr auf einen Int. Warum nicht zu verdoppeln? –

+0

Mein Lesen der bereitgestellten Links: bytes.com Link scheint größtenteils falsch zu sein (tatsächlich haben sich die Dinge seit GCC 4.x veröffentlicht), während SO Link scheint in Ordnung zu sein. Siehe C99, "6.5 Expressions", Abschnitt 7. – Dummy00001

+0

Ich bin ein bisschen verwirrt wegen der Fehlermeldung, weil ich dachte, dass Alias-Regeln char-Typen ausschließen (dh ein 'char'-Pointer darf immer andere Pointer aliasieren, es sei denn, es ist restricte 'ed.) Vielleicht musst du es" unsigned char "machen, damit es sich bewerben kann ..? Ich wäre daran interessiert, die richtige Antwort zu sehen. –

Antwort

25

Es viel aussieht, als ob Sie wirklich verwenden fread wollen:

int data; 
fread(&data, sizeof(data), 1, stream); 

Das heißt, wenn Sie die Route der Lese Zeichen gehen wollen, dann sie als int, der sichere Weg neu zu interpretieren zu tun Sie es in C (aber nicht in C++) ist eine Vereinigung verwenden:

union 
{ 
    char theChars[4]; 
    int theInt; 
} myunion; 

for(int i=0; i<4; i++) 
    myunion.theChars[i] = fgetc(stream); 
return myunion.theInt; 

ich bin nicht sicher, warum die Länge data in Ihrem ursprünglichen Code ist 3. ich nehme an, Sie 4 Byte wollten; zumindest kenne ich keine Systeme, bei denen ein int 3 Bytes ist.

Beachten Sie, dass sowohl Ihr Code als auch meins in hohem Maße nicht portabel sind.

Edit: Wenn Sie Ints verschiedener Längen aus einer Datei lesen wollen, portabel, versuchen, etwas wie folgt aus:

unsigned result=0; 
for(int i=0; i<4; i++) 
    result = (result << 8) | fgetc(stream); 

(Anmerkung: In einem echten Programm, Sie würden zusätzlich den Rückgabewert testen wollen von fgetc() gegen EOF.)

Dies liest ein 4-Byte vorzeichenlos aus der Datei im Little-Endian-Format, unabhängig von, was die Endianess des Systems ist. Es sollte in fast jedem System funktionieren, in dem ein unsigned mindestens 4 Byte ist.

Wenn Sie endian-neutral sein wollen, verwenden Sie keine Zeiger oder Vereinigungen; Verwenden Sie stattdessen Bit-Shifts.

+6

+1. Um es noch einmal zu betonen: Eine Union ist ein offizieller Weg, um den Code strikt Aliasing-konform zu halten. Dies ist nicht gcc-spezifisch, es ist nur gcc's Optimizer ist in der Hinsicht mehr gebrochen. Die Warnungen sollten nicht ignoriert werden: Entweder deaktivieren Sie explizit die -fstrict-aliasing-Optimierung oder korrigieren Sie den Code. – Dummy00001

+0

Ich habe das '3-Byte-Int' behoben. Wäre eine Gewerkschaft tragbar? – Framester

+1

@Framester: Hängt davon ab, auf was Sie portieren möchten. Die meisten Desktop-Systeme und kin bedeuten dasselbe mit einem 32-Bit "int", aber einige sind Big-Endian und einige sind Small-Endian, was bedeutet, dass die Reihenfolge der Bytes im "int" variieren kann. –

1

Grundsätzlich können Sie lesen GCC-Nachricht als Kerl Sie suchen nach Ärger, nicht sagen, ich habe Sie nicht warnen.

Casting eines drei Byte-Zeichen-Array zu einem int ist eines der schlimmsten Dinge, die ich je gesehen habe. Normalerweise hat Ihr int mindestens 4 Bytes. Also für die vierte (und vielleicht mehr wenn int ist breiter) erhalten Sie zufällige Daten. Und dann werfen Sie das alles auf eine double.

Tun Sie nichts davon. Das Alias-Problem, vor dem gcc warnt, ist unschuldig im Vergleich zu dem, was Sie tun.

+4

Hallo, ich habe das Spielzeugbeispiel durch die echte Funktion ersetzt. Und das int mit 3 Bytes war nur ein Tippfehler von mir. – Framester

-4

Anscheinend erlaubt der Standard, dass sizeof (char *) sich von sizeof (int *) unterscheidet, also beschwert sich gcc, wenn Sie einen direkten Cast versuchen. void * ist etwas Besonderes, da alles von und nach void * hin und her konvertiert werden kann. In der Praxis kenne ich nicht viele Architektur/Compiler, wo ein Zeiger nicht immer für alle Typen gleich ist, aber gcc ist richtig, um eine Warnung auszugeben, auch wenn es nervig ist.

denke ich, der sichere Weg

int i, *p = &i; 
char *q = (char*)&p[0]; 

oder

char *q = (char*)(void*)p; 
wäre

Sie können dies auch versuchen und sehen, was Sie bekommen:

char *q = reinterpret_cast<char*>(p); 
+3

'reininterpret_cast' ist C++. Dies ist C. – ptomato

+3

"_der Standard erlaubt es, dass sizeof (char *) von sizeof (int *) _" abweicht oder sie könnten die gleiche Größe, aber unterschiedliche Repräsentation haben, aber das hat hier nichts mit dem Problem zu tun. Bei dieser Frage geht es um Typ-Punning, nicht um Pointer-Darstellung. "' char * q = (char *) & p [0] '" Das Problem besteht nicht darin, wie zwei Zeiger unterschiedlicher Typen auf dieselbe Adresse zeigen. Diese Frage bezieht sich auf Typ-Punning, nicht auf Pointer-Casts. – curiousguy

7

eine Vereinigung zu verwenden ist nicht das Richtige hier zu tun. Das Lesen von einem ungeschriebenen Mitglied der Union ist undefiniert - d. H. Der Compiler ist frei, Optimierungen durchzuführen, die Ihren Code brechen (wie zum Beispiel die Optimierung des Schreibens).

+0

"_von einem ungeschriebenen Mitglied der Union ist undefined_" In diesem einfachen Fall: 'Union U {int i; kurze Hose; } u; u.s = 1; zurück u.i; ', ja. Im Allgemeinen kommt es darauf an. – curiousguy

+2

In C ist die Union wohldefiniertes Verhalten; in C++ ist es undefiniertes Verhalten. –

36

Das Problem tritt auf, weil Sie ein char-Array durch einen double* Zugang:

char data[8]; 
... 
return *(double*)data; 

Aber gcc geht davon aus, dass Ihr Programm obwohl Zeiger von einem anderen Typ nie Variablen zugreifen. Diese Annahme wird streng Aliasing und ermöglicht es dem Compiler aufgerufen einige Optimierungen zu machen:

Wenn der Compiler weiß, dass Ihr *(double*) mit data[] in keiner Weise überlappen können, es zu allen möglichen Dingen erlaubt ist wie Ihr Code Neuordnen in:

return *(double*)data; 
for(int i=7;i>=0;i--) 
    data[i] = fgetc(stream); 

Die Schleife meisten wird wahrscheinlich wegoptimiert und Sie am Ende mit nur:

return *(double*)data; 

Welche Daten verlässt [] nicht initialisiert. In diesem speziellen Fall könnte der Compiler sehen, dass sich Ihre Zeiger überschneiden, aber wenn Sie ihn als char* data deklariert hätten, könnte dies zu Fehlern geführt haben.

Die Strict-Aliasing-Regel besagt jedoch, dass char * und void * auf einen beliebigen Typ zeigen können. So können Sie es in:

double data; 
... 
*(((char*)&data) + i) = fgetc(stream); 
... 
return data; 

Strict Aliasing Warnungen sind wirklich wichtig zu verstehen oder zu beheben. Sie verursachen die Arten von Fehlern, die nicht im Haus reproduziert werden können, weil sie nur auf einem bestimmten Compiler auf einem bestimmten Betriebssystem auf einer bestimmten Maschine und nur auf Vollmond und einmal im Jahr usw. auftreten.

0

Die Autoren Der C-Standard wollte, dass Compiler-Schreiber in Situationen, in denen es theoretisch möglich wäre, effizienten Code generieren würden, aber unwahrscheinlich, dass der Zugriff auf eine globale Variable mit einem scheinbar nicht zusammenhängenden Zeiger möglich ist.Die Idee war nicht zu verbieten Typ punning durch Gießen und einen Zeiger in einem einzigen Ausdruck dereferencing, sondern vielmehr, dass gegeben, etwas zu sagen wie:

int x; 
int foo(double *d) 
{ 
    x++; 
    *d=1234; 
    return x; 
} 

ein Compiler davon ausgehen würde, dass der Schreibvorgang auf * d gewonnen nicht beeinflussen x. Die Autoren des Standards wollten Situationen auflisten, in denen eine Funktion wie die obige, die einen Zeiger von einer unbekannten Quelle erhielt, annehmen müsste, dass sie ein scheinbar nicht verwandtes globales Alias ​​aliasieren könnte, ohne dass die Typen perfekt übereinstimmen müssten. Während die Argumentation stark darauf hindeutet, dass Autoren des Standards einen Standard für die Mindestkonformität in Fällen beschreiben möchten, in denen ein Compiler ansonsten keinen Grund zu der Annahme hätte, dass die Aliasnamen lauten könnten, erfordert die Regel nicht, dass Compiler das Aliasing erkennen Fälle, in denen es offensichtlich ist und die Autoren von gcc haben beschlossen, dass sie lieber das kleinste Programm generieren, während es der schlecht geschriebenen Sprache des Standards entspricht, als Code generieren, der tatsächlich nützlich ist, und anstatt Aliasing in zu erkennen Fälle, in denen es offensichtlich ist (während man immer noch davon ausgehen kann, dass Dinge nicht so aussehen, als würden sie Aliasnamen annehmen), würden sie eher verlangen, dass Programmierer memcpy verwenden, was einen Compiler erfordert, der die Möglichkeit bietet, dass Zeiger von unbekannter Ursprung alias fast alles, also imped Optimierung.

4

Dieses Dokument fasst die Situation: http://dbp-consulting.com/tutorials/StrictAliasing.html

Es gibt verschiedene Lösungen gibt, aber die meisten tragbaren/safe ist memcpy() zu verwenden. (. Die Funktionsaufrufe optimiert werden kann, so ist es nicht so ineffizient wie es scheint) Ersetzen Sie beispielsweise diese:

return *(short*)data; 

mit diesem:

short temp; 
memcpy(&temp, data, sizeof(temp)); 
return temp; 
+0

das ist die beste Antwort. – Bob

Verwandte Themen