webentwicklung-frage-antwort-db.com.de

Ist std :: memcpy zwischen verschiedenen trivial kopierbaren Typen undefiniert?

Ich benutze seit langer Zeit std::memcpy, Um strict aliasing zu umgehen.

Beispiel: Sie untersuchen ein float wie this :

float f = ...;
uint32_t i;
static_assert(sizeof(f)==sizeof(i));
std::memcpy(&i, &f, sizeof(i));
// use i to extract f's sign, exponent & significand

Dieses Mal habe ich den Standard überprüft und nichts gefunden, was dies bestätigt. Alles was ich gefunden habe ist dies :

Unabhängig davon, ob das Objekt einen gültigen Wert vom Typ T enthält oder nicht, können die zugrunde liegenden Bytes ([intro.memory]), aus denen sich das Objekt zusammensetzt, für jedes Objekt (mit Ausnahme eines möglicherweise überlappenden Unterobjekts) des einfach kopierbaren Typs T in ein kopiert werden Array von char, unsigned char oder std :: byte ([cstddef.syn]).40 Wenn der Inhalt dieses Arrays zurück in das Objekt kopiert wird, behält das Objekt anschließend seinen ursprünglichen Wert. [Beispiel:

#define N sizeof(T)
char buf[N];
T obj;                          // obj initialized to its original value
std::memcpy(buf, &obj, N);      // between these two calls to std​::​memcpy, obj might be modified
std::memcpy(&obj, buf, N);      // at this point, each subobject of obj of scalar type holds its original value

- Ende Beispiel]

und this :

Für jeden trivial kopierbaren Typ T, wenn zwei Zeiger auf T auf unterschiedliche T-Objekte obj1 und obj2 zeigen, wobei weder obj1 noch obj2 ein potenziell überlappendes Unterobjekt sind, wenn die zugrunde liegenden Bytes ([intro.memory]), die obj1 bilden, kopiert werden obj2,41 obj2 soll anschließend den gleichen Wert wie obj1 haben. [Beispiel:

T* t1p;
T* t2p;
// provided that t2p points to an initialized object ...
std::memcpy(t1p, t2p, sizeof(T));
// at this point, every subobject of trivially copyable type in *t1p contains
// the same value as the corresponding subobject in *t2p

- Ende Beispiel]

Das std::memcpy - Setzen eines float von/nach char[] Ist zulässig, und das std::memcpy - Setzen zwischen denselben einfachen Typen ist ebenfalls zulässig.

Ist mein erstes Beispiel (und die damit verbundene Antwort) gut definiert? Oder die richtige Möglichkeit, ein float zu untersuchen, besteht darin, es std::memcpy In einen unsigned char[] - Puffer zu kopieren und mit shifts und ors ein zu erstellen uint32_t Davon?


Hinweis: Wenn Sie sich die Garantien von std::memcpy Ansehen, wird diese Frage möglicherweise nicht beantwortet. Soweit ich weiß, könnte ich std::memcpy Durch eine einfache Bytekopieschleife ersetzen, und die Frage wird dieselbe sein.

51
geza

Der Standard sagt möglicherweise nicht richtig, dass dies zulässig ist, aber es wird mit ziemlicher Sicherheit angenommen, und nach meinem Wissen werden alle Implementierungen dies als definiertes Verhalten behandeln.

Um das Kopieren in ein tatsächliches char[N]-Objekt zu erleichtern, kann auf die Bytes zugegriffen werden, die das f -Objekt bilden, als ob sie ein char[N] wären. Dieser Teil ist meines Erachtens nicht umstritten.

Bytes aus einem char[N], die einen uint32_t-Wert darstellen, können in ein uint32_t-Objekt kopiert werden. Auch dieser Teil ist meines Erachtens unbestritten.

Ebenso unbestritten ist meiner Meinung nach, dass z. fwrite hat die Bytes möglicherweise in einem Programmlauf geschrieben, und fread hat sie möglicherweise in einem anderen Lauf oder sogar in einem anderen Programm vollständig zurückgelesen.

Aufgrund des letzten Teils denke ich, dass es keine Rolle spielt, woher die Bytes stammen, solange sie eine gültige Darstellung eines uint32_t-Objekts bilden. Sie könnten alle float - Werte mit memcmp durchlaufen haben, bis Sie die gewünschte Darstellung erhalten haben, von der Sie wussten, dass sie mit der des uint32_t-Werts identisch ist du interpretierst es als. Sie könnten haben das sogar in einem anderen Programm gemacht, einem Programm, das der Compiler noch nie gesehen hat. Das wäre gültig gewesen.

Wenn Ihr Code aus Sicht der Implementierung nicht von eindeutig gültigem Code zu unterscheiden ist, muss Ihr Code als gültig angesehen werden.

21
user743382

Ist mein erstes Beispiel (und die damit verbundene Antwort) gut definiert?

Das Verhalten ist nicht undefiniert (es sei denn, der Zieltyp verfügt über Trap-Darstellungen die vom Quelltyp nicht gemeinsam genutzt werden), aber der resultierende Wert der Ganzzahl ist implementierungsdefiniert. Standard gibt keine Garantie dafür, wie Gleitkommazahlen dargestellt werden. Daher gibt es keine Möglichkeit, Mantissen usw. portabel aus der Ganzzahl zu extrahieren. Das heißt, Sie beschränken sich heutzutage nicht mehr auf IEEE 754, wenn Sie Systeme verwenden.

Probleme bei der Portabilität:

  • IEEE 754 wird von C++ nicht garantiert
  • Es ist nicht garantiert, dass die Byte-Endianze von float mit der ganzzahligen Endianze übereinstimmt.
  • (Systeme mit Fallendarstellungen).

Sie können std::numeric_limits::is_iec559, um zu überprüfen, ob Ihre Vermutung über die Darstellung korrekt ist.

 Obwohl es scheint, dass uint32_t kann keine Fallen haben (siehe Kommentare), also brauchen Sie sich keine Sorgen zu machen. Durch die Nutzung uint32_t Sie haben die Portierbarkeit auf esoterische Systeme bereits ausgeschlossen. Standardkonforme Systeme müssen diesen Alias ​​nicht definieren.

18
eerorika

Ihr Beispiel ist klar definiert und unterbricht kein striktes Aliasing. std::memcpy Gibt eindeutig an:

Kopiert count Bytes von dem Objekt, auf das src zeigt, zu dem Objekt, auf das dest zeigt. Beide Objekte werden als Arrays von unsigned char Neu interpretiert.

Der Standard erlaubt das Aliasing eines beliebigen Typs durch einen (signed/unsigned) char* Oder std::byte Und daher weist Ihr Beispiel kein UB auf. Ob die resultierende Ganzzahl irgendeinen Wert hat, ist eine andere Frage.


use i to extract f's sign, exponent & significand

Dies wird jedoch vom Standard nicht garantiert, da der Wert von float implementierungsdefiniert ist (im Fall von IEEE 754 funktioniert dies jedoch).

14