2016-09-22 2 views
1

Ich bin verwirrt darüber, ob das eine strenge Aliasing-Verletzung ist oder nicht und ruft nicht definiert Verhalten auf. Mehrere Leute haben mir gesagt, dass es keine Verletzung ist und dass es keine UB gibt, aber aus dem Lesen der C++ - Spezifikation klingt es nach irgendetwas, das einen typgegossenen Zeiger dereferenziert (es sei denn, es wurde nur cv-casted oder casted zu einem kompatiblen Typ oder char) ist UB.Strict Aliasing char * zu einem breiteren Typ ohne undefiniertes Verhalten

#define SWAP(x) (((x) << 8) | ((x) >> 8))) 

char* foo(char* data, size_t len16) { 
    int align = reinterpret_cast<uintptr_t>(data) % sizeof(uint16_t); 
    if (align == 0) { 
    uint16_t* data16 = reinterpret_cast<uint16_t*>(data); 
    for (size_t i = 0; i < len16; i++) { 
     data16[i] = SWAP(data16[i]); 
    } 
    } else { 
    throw "Unaligned"; 
    } 

    return data; 
} 

https://godbolt.org/g/DIXtJX

(Dies ist eine etwas konstruiertes Beispiel, in Wirklichkeit SWAP eine Fremd Funktion sein, die eine uint16_t erfordert.)

Ist die Geschichte verändert, weil wir die Ausrichtung überprüft haben , sind Sie sich über die Größe der Typen sicher und kümmern sich nicht um Endianness? Die verbleibende Sorge wäre, glaube ich, eine tote Code-Eliminierung durch den Optimierer.

Wenn dies illegal ist, wie würden Sie effizient einen char Puffer (z. B. aus einer Datei) als seinen beabsichtigten Typ interpretieren (z. B. int16 s)? Ich kenne durch eine Vereinigung Gießen, aber ich weiß wirklich nicht, wie das ist anders (siehe casting through a union(1), abgesehen von sagen dem Compiler, dass es nicht Beseitigung von totem Code tun können.

+4

Warum der Downvote? Bitte lass ein Kommentar da. – ZachB

Antwort

4

Mehrere Leute haben mir gesagt, es ist keine Verletzung und dass es keine UB

ist

Diese Leute sind falsch. Dieser Code:

uint16_t* data16 = reinterpret_cast<uint16_t*>(data); 

Verhalten ist definiert, wenn und nur dann, wenn, gibt es ein Objekt vom Typ uint16_t bei data. Das heißt, wenn ich Ihre foo wie genannt:

uint16_t p = 42; 
foo(reinterpret_cast<char*>(&p), 2); // now, we're ok 

oder:

char data[64]; 
new (data) uint16_t{42}; 
foo(data, 2); // also ok, though with C++17 you'll have to use std::launder 

Aber sonst ist es UB. Die Nicht-UB Art und Weise, dies zu tun wäre:

uint16_t data16; 
memcpy(&data16, data, sizeof(data16)); 

, die viele Compiler als reinterpret_cast behandeln.


Das heißt, eine enorme Menge Code der Vernetzung tut, was Sie tun, und es würde Unruhen in den Straßen viele wütend Beiträge geschrieben auf Foren, wenn Compiler diesen Code in so optimiert, dass es verursacht mach nicht was du willst.

+0

Danke für die hilfreiche Antwort. Re: die Nicht-UB-Methode, es sieht so aus als würde g ++ das wie eine 'reininterpret_cast' für -Os, -O1 und -O2 behandeln, aber diese Methode scheint zu verhindern, dass das Loop mit -O3 abgerollt wird. https://godbolt.org/g/KHwOXi (Nicht getestet, wenn das tatsächlich ein Deopt ist.) – ZachB

1

tun etwas dass Dereferenzierungen (nur cV-gegossen oder auf einem kompatiblen Typ oder char gegossen, es sei denn es wurde) ein Typ-gegossene Zeiger UB.

die nicht korrekt ist.

Jeder Zeiger kann auf char* und void* gegossen werden und zurück Es ist UB nur, wenn der ursprüngliche Zeiger von einem anderen Typ ist als der Zeigertyp, der beim Dereferenzieren des Zeigers verwendet wird. Es gibt Ausnahmen zu sogar diesem. Ein Beispiel finden Sie in Struct alignment and type reinterpretation.

+0

Richtig, okay, aber wenn der ursprüngliche Zeiger ein 'char *' ist, dann kann er nicht z. ein 'uint16_t'? Oder können wir sagen, dass der "ursprüngliche Zeiger" ein "uint16_t" war, weil z.B. eine andere ausführbare Datei schrieb 'uint16_t's? – ZachB

+0

@ZachB, sprichst du über Daten, die über ein Netzwerk empfangen oder aus einer Datei gelesen wurden? –

+0

Entweder, aber wir kennen die Endianness, wenn Sie das verstehen. – ZachB

Verwandte Themen