Wann ist char* sicher für striktes Pointer-Aliasing?

Lesezeit: 6 Minuten

Benutzer-Avatar
Doug T.

Ich habe versucht, die strengen Aliasing-Regeln zu verstehen, die für den Zeichenzeiger gelten.

Hier das ist angegeben:

Es wird immer davon ausgegangen, dass sich ein char* auf einen Alias ​​eines beliebigen Objekts beziehen kann.

Ok, im Kontext des Socket-Codes kann ich Folgendes tun:

struct SocketMsg
{
   int a;
   int b;
};

int main(int argc, char** argv)
{
   // Some code...
   SocketMsg msgToSend;
   msgToSend.a = 0;
   msgToSend.b = 1;
   send(socket, (char*)(&msgToSend), sizeof(msgToSend);
};

Aber dann gibt es diese Aussage

Die Umkehrung ist nicht wahr. Das Umwandeln eines char* in einen Zeiger eines anderen Typs als char* und dessen Dereferenzierung verstößt normalerweise gegen die strikte Aliasing-Regel.

Bedeutet dies, dass ich beim Empfangen eines Char-Arrays die Umwandlung in eine Struktur nicht neu interpretieren kann, wenn ich die Struktur der Nachricht kenne:

struct SocketMsgToRecv
{
    int a;
    int b;
};

int main()
{
    SocketMsgToRecv* pointerToMsg;
    char msgBuff[100];
    ...
    recv(socket, msgBuff, 100);
    // Ommiting make sure we have a complete message from the stream
    // but lets assume msgBuff[0]  has a complete msg, and lets interpret the msg

    // SAFE!?!?!?
    pointerToMsg = &msgBuff[0];

    printf("Got Msg: a: %i, b: %i", pointerToMsg->a, pointerToMsg->b);
}

Wird dieses zweite Beispiel nicht funktionieren, weil der Basistyp ein Char-Array ist und ich es in eine Struktur umwandle? Wie gehen Sie mit dieser Situation in einer streng aliasierten Welt um?

  • Erfordert das zweite Stück Code nicht, dass Sie explizit casr ausführen? Hast du alle Warnungen aktiviert?

    – Ajay Brahmakshatriya

    13. November 2017 um 2:40 Uhr

Zu @Adam Rosenfield: Die Gewerkschaft wird eine Angleichung erreichen, solange der Lieferant des Char* damit begonnen hat, etwas Ähnliches zu tun.

Es kann nützlich sein, einen Schritt zurückzutreten und herauszufinden, worum es hier geht.

Die Grundlage für die Aliasing-Regel ist die Tatsache, dass Compiler Werte verschiedener einfacher Typen an verschiedenen Speichergrenzen platzieren können, um den Zugriff zu verbessern, und dass Hardware in einigen Fällen eine solche Ausrichtung erfordern kann, um den Zeiger überhaupt verwenden zu können. Dies kann auch in Strukturen auftreten, in denen es eine Vielzahl unterschiedlich großer Elemente gibt. Die Struktur kann an einer guten Grenze begonnen werden. Darüber hinaus kann der Compiler immer noch Slack Bites in das Innere der Struktur einführen, um eine ordnungsgemäße Ausrichtung der Strukturelemente zu erreichen, die dies erfordern.

Wenn man bedenkt, dass Compiler oft Optionen haben, um zu steuern, wie all dies gehandhabt wird oder nicht, können Sie sehen, dass es viele Möglichkeiten gibt, wie Überraschungen auftreten können. Dies ist besonders wichtig, wenn Zeiger auf Strukturen (als char* umgewandelt oder nicht) an Bibliotheken übergeben werden, die kompiliert wurden, um unterschiedliche Ausrichtungskonventionen zu erwarten.

Was ist mit Char*?

Die Vermutung über char* ist, dass sizeof(char) == 1 (relativ zu den Größen aller anderen Daten mit Größenangabe) und dass char*-Zeiger keine Ausrichtungsanforderung haben. Ein echtes Zeichen* kann also immer sicher herumgereicht und erfolgreich verwendet werden, ohne sich Gedanken über die Ausrichtung machen zu müssen, und das gilt für jedes Element eines Zeichens[] Array, Ausführen von ++ und — auf den Zeigern und so weiter. (Seltsamerweise ist void* nicht ganz dasselbe.)

Jetzt sollten Sie in der Lage sein zu sehen, wie Sie eine Art Strukturdaten in ein Zeichen übertragen[] Array, das selbst nicht richtig ausgerichtet war, kann der Versuch, auf einen Zeiger zurückzusetzen, der Ausrichtung(en) erfordert, ein ernsthaftes Problem darstellen.

Wenn Sie eine Vereinigung von char machen[] Array und einer Struktur, wird die anspruchsvollste Ausrichtung (dh die der Struktur) vom Compiler berücksichtigt. Dies funktioniert, wenn der Lieferant und der Verbraucher effektiv übereinstimmende Verbindungen verwenden, so dass das Casting der Struktur * in char * und zurück einwandfrei funktioniert.

In diesem Fall würde ich hoffen, dass die Daten in einer ähnlichen Union erstellt wurden, bevor der Zeiger darauf in char* umgewandelt oder auf andere Weise als Array von sizeof(char) Bytes übertragen wurde. Es ist auch wichtig sicherzustellen, dass alle Compileroptionen zwischen den verwendeten Bibliotheken und Ihrem eigenen Code kompatibel sind.

  • sind alle 3 char, signed char, unsigned char OK für Aliasing? und mit jeder Lebenslauf-Qualifikations-Kombination auch ?

    – v.oddou

    22. April 2015 um 3:36 Uhr

  • Die Aliasing-Regeln haben nichts mit der Ausrichtung zu tun. Gemäß der C89-Begründung, angesichts globaler Erklärungen wie int i; float *fp;der Zweck besteht darin, Compilern die Beibehaltung zu ermöglichen i in einem Register über Zugriffe auf *fp. Die Idee war, dass ein Compiler nicht pessimistisch davon ausgehen sollte, dass ein Write-to *fp könnte sich ändern i wenn es keinen Grund zu erwarten gab das *fp würde auf etwas zeigen, das kein war float*. Ich glaube nicht, dass die Regel jemals dazu gedacht war, Compiler Fälle ignorieren zu lassen, in denen Aliasing offensichtlich ist (die Adresse eines Objekts sollte einem Compiler einen starken Hinweis geben …

    – Superkatze

    23. Juni 2016 um 19:06 Uhr

  • …dass auf das betreffende Objekt per Pointer zugegriffen werden soll, und Casting an int* zu einem float* sollte dem Compiler einen starken Hinweis darauf geben, dass an int wird wahrscheinlich durch Schreiben in a geändert float*aber gcc fühlt sich nicht mehr verpflichtet, solche Dinge zu bemerken.

    – Superkatze

    23. Juni 2016 um 19:14 Uhr

  • Striktes Aliasing liegt nicht an der Ausrichtungsanforderung. Wie kann diese Antwort 9 Upvotes bekommen?

    – Ajay Brahmakshatriya

    13. November 2017 um 2:37 Uhr

Richtig, das zweite Beispiel verstößt gegen die strengen Aliasing-Regeln, also wenn man mit dem kompiliert -fstrict-aliasing Flag, besteht die Möglichkeit, dass Sie einen falschen Objektcode erhalten. Die vollständig korrekte Lösung wäre, hier eine Union zu verwenden:

union
{
  SocketMsgToRecv msg;
  char msgBuff[100];
};

recv(socket, msgBuff, 100);

printf("Got Msg: a: %i, b: %i", msg.a, msg.b);

  • Entspricht dies dem Standard oder lässt Sie der Compiler nur davonkommen, an ein Mitglied zu schreiben und von einem anderen zu lesen?

    – Alex B

    6. Mai 2010 um 0:26 Uhr

  • Die Gewerkschaft ist völlig unnötig. Übergeben Sie einfach einen Zeiger auf die Struktur (cast to char *) zu recv.

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

    3. September 2010 um 1:38 Uhr

  • Beachten Sie, dass -fstrict-aliasing ist standardmäßig bei eingeschaltet -O2 und höher in gcc

    – MM

    13. November 2017 um 3:07 Uhr

  • @R..GitHubSTOPHELPINGICE Könnten Sie das bitte näher erläutern?

    – Nubkuchen

    9. September 2020 um 12:10 Uhr

  • An dieser Antwort sind mehrere Dinge falsch. (1) recv hat vier Argumente. (2) Hier ist kein Typ-Wortspiel über eine Union erforderlich. (3) Aber wir müssen auch nicht werfen, im Gegensatz zu dem, was ein vorheriger Kommentar sagte. (4) Tatsächlich ist die herkömmliche Verwendung so einfach wie SocketMsgToRecv msg; ssize_t ret = recv(socket, &msg, sizeof msg, flags); — und natürlich müssen wir immer mit Fehlern umgehen. (Mir ist klar, dass dies eine alte Antwort ist, aber es ist einer der Top-Hits bei Google.)

    – Konrad Rudolf

    15. Februar um 14:56 Uhr

1362660cookie-checkWann ist char* sicher für striktes Pointer-Aliasing?

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

Privacy policy