Das Dereferenzieren von typgesponnenen Zeigern verstößt gegen strikte Aliasing-Regeln

Lesezeit: 9 Minuten

Benutzeravatar von Framester
Framester

Ich habe den folgenden Codeabschnitt verwendet, um Daten aus Dateien als Teil eines größeren Programms zu lesen.

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 wird mir gesagt, zu verwenden -O2 und ich erhalte folgende gcc-Warnung:
warning: dereferencing type-punned pointer will break strict-aliasing rules

Beim Googlen fand ich zwei orthogonale Antworten:

vs

  • Wenn Sie also ein int* und ein float* haben, dürfen sie grundsätzlich nicht auf denselben Speicherort zeigen. Wenn Ihr Code dies nicht respektiert, wird der Optimierer des Compilers höchstwahrscheinlich Ihren Code beschädigen.

Schließlich will ich die Warnungen nicht ignorieren. Was würdest du empfehlen?

[update] Ich habe das Spielzeugbeispiel durch die echte Funktion ersetzt.

  • Ihre Funktion gibt ein Double zurück, aber Sie wandeln Ihre Rückgabe in ein Int um. Warum nicht auf Double werfen?

    – Adam Schiemke

    14. Juli 2010 um 13:09 Uhr

  • Mein Lesen der bereitgestellten Links: Der bytes.com-Link scheint größtenteils falsch zu sein (eigentlich haben sich die Dinge seit der Veröffentlichung von GCC 4.x geändert), während der SO-Link in Ordnung zu sein scheint. Siehe C99, „6.5 Ausdrücke“, Abschnitt 7.

    – Dummy00001

    14. Juli 2010 um 14:25 Uhr

  • Ich bin etwas verwirrt von der Fehlermeldung, weil ich dachte, Aliasing-Regeln ausgeschlossen char Typen (also ein char pointer ist es immer erlaubt, andere Pointer zu aliasieren, es sei denn, dies ist der Fall restricted.) Vielleicht muss man es schaffen unsigned char damit das zutrifft..? Mich würde die richtige Antwort interessieren.

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

    14. Juli 2010 um 14:53 Uhr

  • @RA char * kann alles als Alias ​​verwenden, aber nicht umgekehrt. Er wirft und dereferenziert char in short,int,float und double im obigen Code.

    – 5 Pfund

    14. Juli 2010 um 16:50 Uhr

Benutzeravatar von Lasse Reinhold
Lass Reinhold

Das Problem tritt auf, weil Sie über a auf ein Zeichenarray zugreifen double*:

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

Aber gcc geht davon aus, dass Ihr Programm niemals über Zeiger eines anderen Typs auf Variablen zugreifen wird. Diese Annahme wird als striktes Aliasing bezeichnet und ermöglicht es dem Compiler, einige Optimierungen vorzunehmen:

Wenn der Compiler weiß, dass Ihre *(double*) darf sich in keiner Weise überschneiden data[]ist es für alle möglichen Dinge erlaubt, z. B. das Umordnen Ihres Codes in:

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

Die Schleife wird höchstwahrscheinlich wegoptimiert und Sie erhalten am Ende nur:

return *(double*)data;

Was Ihre Daten hinterlässt[] nicht initialisiert. In diesem speziellen Fall könnte der Compiler sehen, dass sich Ihre Zeiger überlappen, aber wenn Sie es deklariert hätten char* dataes hätte Fehler geben können.

Die strikte Aliasing-Regel besagt jedoch, dass ein char* und void* auf jeden Typ zeigen können. Sie können es also umschreiben in:

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

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

Benutzeravatar von Martin B
MartinB

Es sieht sehr danach aus, als ob Sie wirklich fread verwenden möchten:

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

Das heißt, wenn Sie den Weg des Lesens von Zeichen gehen möchten, dann interpretieren Sie sie als int neu, der sichere Weg, dies in C zu tun (aber nicht in C++) ist die Verwendung einer Union:

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

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

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

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

Bearbeiten: Wenn Sie Ints unterschiedlicher Länge aus einer Datei portabel lesen möchten, versuchen Sie Folgendes:

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

(Hinweis: In einem echten Programm möchten Sie zusätzlich den Rückgabewert von fgetc() gegen EOF testen.)

Dies liest ein 4-Byte-Unsigned aus der Datei im Little-Endian-Format, trotzdem was die Endianness des Systems ist. Es sollte auf fast jedem System funktionieren, auf dem ein unsigned mindestens 4 Bytes lang ist.

Wenn Sie Endian-neutral sein möchten, verwenden Sie keine Zeiger oder Vereinigungen; Verwenden Sie stattdessen Bitverschiebungen.

  • +1. Um es noch einmal zu betonen: Eine Union ist eine offizielle Möglichkeit, den Code strikt Aliasing-konform zu halten. Dies ist nicht gcc-spezifisch, es ist nur der Optimierer von gcc, der in dieser Hinsicht kaputter ist. Die Warnungen sollten nicht ignoriert werden: Deaktivieren Sie entweder explizit die -fstrict-aliasing-Optimierung oder korrigieren Sie den Code.

    – Dummy00001

    14. Juli 2010 um 14:17 Uhr

  • @Framester: Hängt davon ab, worauf Sie portieren möchten. Die meisten Desktop-Systeme und Verwandte meinen dasselbe mit 32-Bit intaber einige sind Big-Endian und andere Small-Endian, was die Reihenfolge der Bytes in der bedeutet int kann variieren.

    – David Thornley

    14. Juli 2010 um 16:56 Uhr

  • @David: Nur um eine Kleinigkeit herauszupicken: Der übliche Begriff ist “Little-Endian”.

    –Martin B

    15. Juli 2010 um 8:12 Uhr

  • @Dummy00001 “Eine Vereinigung ist eine offizielle Möglichkeit, den Code strikt Aliasing-konform zu halten.” Laut WHO?

    – Neugieriger

    3. Oktober 2011 um 18:42 Uhr

  • @kestasx siehe §6.2.6.1 ¶7: die Bytes … die diesem Mitglied nicht entsprechen, aber anderen Mitgliedern entsprechen, nehmen nicht spezifizierte Werte an, was impliziert, dass Bytes neu interpretiert werden können, indem ein anderer Member gelesen wird. Dies war auch Gegenstand einer Korrektur in ISO C99 TC3 (DR283)

    – ninjalj

    13. April 2015 um 18:52 Uhr

Die Verwendung einer Gewerkschaft ist nicht hier das Richtige tun. Das Lesen aus einem ungeschriebenen Mitglied der Union ist undefiniert – dh der Compiler kann Optimierungen durchführen, die Ihren Code beschädigen (wie das Optimieren des Schreibens).

  • von einem ungeschriebenen Mitglied der Gewerkschaft ist undefiniert„In diesem einfachen Fall: union U { int i; short s; } u; u.s=1; return u.i;, Jawohl. Generell kommt es darauf an.

    – Neugieriger

    3. Oktober 2011 um 20:03 Uhr

  • In C ist die Vereinigung wohldefiniertes Verhalten; in C++ ist es undefiniertes Verhalten.

    – MM

    24. Dezember 2014 um 9:46 Uhr

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

Es gibt dort mehrere verschiedene Lösungen, aber die portabelste/sicherste ist die Verwendung von memcpy(). (Die Funktionsaufrufe können optimiert werden, sodass es nicht so ineffizient ist, wie es scheint.) Ersetzen Sie beispielsweise Folgendes:

return *(short*)data;

Mit diesem:

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

Grundsätzlich können Sie die Nachricht von gcc lesen als Mann, du suchst Ärger, sag nicht, ich hätte dich nicht gewarnt.

Umwandeln eines 3-Byte-Zeichenarrays in ein int ist eines der schlimmsten Dinge, die ich je gesehen habe. Normalerweise Ihre int hat mindestens 4 Bytes. Also für die vierte (und vielleicht mehr, wenn int breiter ist) erhalten Sie zufällige Daten. Und dann wirfst du all das auf a double.

Mach einfach nichts davon. Das Aliasing-Problem, vor dem gcc warnt, ist unschuldig im Vergleich zu dem, was Sie tun.

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

    – Framester

    14. Juli 2010 um 16:40 Uhr

Benutzeravatar von Supercat
Superkatze

Die Autoren des C-Standards wollten es Compiler-Autoren ermöglichen, effizienten Code in Situationen zu generieren, in denen es theoretisch möglich, aber unwahrscheinlich wäre, dass auf den Wert einer globalen Variablen mit einem scheinbar nicht verwandten Zeiger zugegriffen wird. Die Idee bestand nicht darin, das Typ-Wortspiel zu verbieten, indem ein Zeiger in einem einzigen Ausdruck gecastet und dereferenziert wird, sondern eher zu sagen, dass bei etwas wie:

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

Ein Compiler wäre berechtigt anzunehmen, dass das Schreiben in *d x nicht beeinflusst. Die Autoren des Standards wollten Situationen auflisten, in denen eine Funktion wie die obige, die einen Zeiger von einer unbekannten Quelle erhält, davon ausgehen müsste, dass sie möglicherweise einen scheinbar nicht verwandten globalen Namen hat, ohne dass die Typen perfekt übereinstimmen müssen. Obwohl die Begründung leider stark darauf hindeutet, dass die Autoren des Standards beabsichtigten, einen Standard für die Mindestkonformität in Fällen zu beschreiben, in denen ein Compiler hätte sonst keinen Grund zu der Annahme, dass die Dinge Alias ​​sein könntenverlangt die Regel nicht, dass Compiler Aliasing erkennen in Fällen, in denen es offensichtlich ist und die Autoren von gcc haben beschlossen, lieber das kleinstmögliche Programm zu generieren, während es der schlecht geschriebenen Sprache des Standards entspricht, als Code zu generieren, der tatsächlich nützlich ist, und anstatt Aliasing in Fällen zu erkennen, in denen es offensichtlich ist (während immer noch davon ausgehen können, dass Dinge, die nicht so aussehen, wie sie aussehen, Alias ​​werden, werden sie es lieber erfordern, dass Programmierer sie verwenden memcpysodass ein Compiler die Möglichkeit berücksichtigen muss, dass Zeiger unbekannter Herkunft fast alles aliasieren können, wodurch die Optimierung behindert wird.

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

    – Framester

    14. Juli 2010 um 16:40 Uhr

Benutzeravatar von Sebastien Mirolo
Sebastian Mirolo

Anscheinend erlaubt der Standard, dass sich sizeof(char*) von sizeof(int*) unterscheidet, also beschwert sich gcc, wenn Sie eine direkte Umwandlung versuchen. void* ist insofern etwas Besonderes, als alles zu und von void* hin und her konvertiert werden kann. In der Praxis kenne ich nicht viele Architekturen/Compiler, bei denen ein Zeiger nicht immer für alle Typen gleich ist, aber gcc gibt zu Recht eine Warnung aus, auch wenn es lästig ist.

Ich denke, der sichere Weg wäre

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

oder

char *q = (char*)(void*)p;

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

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

  • reinterpret_cast ist C++. Das ist C.

    – Tomaten

    16. August 2010 um 8:29 Uhr

  • der Standard erlaubt, dass sich sizeof(char*) von sizeof(int*) unterscheidet” oder sie könnten die gleiche Größe, aber unterschiedliche Darstellung haben, aber das hat sowieso nichts mit dem Problem hier zu tun. Bei dieser Frage geht es um Wortspiele, nicht um die Darstellung von Zeigern. “char *q = (char*)&p[0]“Das Problem ist nicht, wie man zwei Zeiger unterschiedlichen Typs dazu bringt, auf dieselbe Adresse zu zeigen. Bei dieser Frage geht es um Typ-Wortspiele, nicht um Zeigerumwandlungen.

    – Neugieriger

    3. Oktober 2011 um 20:00 Uhr


1405470cookie-checkDas Dereferenzieren von typgesponnenen Zeigern verstößt gegen strikte Aliasing-Regeln

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

Privacy policy