Für das gleiche Programm:

const char* s = "abcd";
auto x1 = reinterpret_cast<const int64_t*>(s);
auto x2 = reinterpret_cast<const char*>(x1);
std::cout << *x1 << std::endl;
std::cout << x2 << std::endl; // Always "abcd"

In gcc5 (Link): 139639660962401
In gcc8 (Link): 1684234849

  1. Warum variiert der Wert je nach Compiler-Version?
  2. Was ist dann eine compilersichere Methode, um von const char * nach int64_t und zurück zu wechseln (genau wie in diesem Problem - nicht für tatsächliche Ganzzahlzeichenfolgen, sondern auch für eine mit anderen Zeichen)?
6
tangy 17 Jän. 2019 im 22:09

3 Antworten

Beste Antwort
  1. Warum variiert der Wert je nach Compiler-Version?

Verhalten ist undefiniert.

  1. Was ist dann eine compilersichere Möglichkeit, von const char * nach int64_t und zurück zu wechseln

Es ist etwas unklar, was Sie unter "von const char * nach int64_t wechseln" verstehen. Anhand des Beispiels gehe ich davon aus, dass Sie eine Zuordnung aus einer Zeichenfolge (mit nicht größerer Länge als Anpassungen) in eine 64-Bit-Ganzzahl erstellen möchten, die mit einem anderen Prozess zurückkonvertiert werden kann - möglicherweise von einem anderen kompiliert (Version von). Compiler.

Erstellen Sie zunächst ein int64_t Objekt und initialisieren Sie es auf Null:

int64_t i = 0;

Holen Sie sich die Länge der Zeichenfolge

auto len = strlen(s);

Überprüfen Sie, ob es passt

assert(len < sizeof i);

Kopieren Sie die Bytes der Zeichenfolge auf die Ganzzahl

memcpy(&i, s, len);

(Solange der Integer-Typ keine Trap-Darstellungen enthält.) Das Verhalten ist gut definiert, und die generierte Ganzzahl ist in allen Compiler-Versionen gleich, solange die CPU-Endianität (und die Darstellung negativer Zahlen) gleich bleibt.

Das Zurücklesen der Zeichenfolge erfordert kein Kopieren, da char ausnahmsweise alle anderen Typen als Alias verwenden darf:

auto back = reinterpret_cast<char*>(&i);

Beachten Sie die Qualifikation im letzten Abschnitt. Diese Methode funktioniert nicht, wenn die Ganzzahl (z. B. über das Netzwerk) an den Prozess übergeben wird, der auf einer anderen CPU ausgeführt wird. Dies kann auch durch Bitverschiebung und Maskierung erreicht werden, sodass Sie Oktette mithilfe von Bitverschiebung und Maskierung an eine bestimmte Position von Bedeutung kopieren.

6
eerorika 17 Jän. 2019 im 21:26

Wenn Sie den int64_t - Zeiger dereferenzieren, liest er über das Ende des Speichers hinaus, der für die Zeichenfolge reserviert ist, aus der Sie gegossen haben. Wenn Sie die Länge der Zeichenfolge auf mindestens 8 Byte ändern, wird der ganzzahlige Wert stabil.

const char* s = "abcdefg"; // plus null terminator
auto x1 = reinterpret_cast<const int64_t*>(s);
auto x2 = reinterpret_cast<const char*>(x1);
std::cout << *x1 << std::endl;
std::cout << x2 << std::endl; // Always "abcd"

Wenn Sie den Zeiger stattdessen in einer Ganzzahl speichern möchten, sollten Sie intptr_t verwenden und das * wie folgt weglassen:

const char* s = "abcd";
auto x1 = reinterpret_cast<intptr_t>(s);
auto x2 = reinterpret_cast<const char*>(x1);
std::cout << x1 << std::endl;
std::cout << x2 << std::endl; // Always "abcd"
2
nate 17 Jän. 2019 im 19:50

Basierend auf dem, worauf RemyLebeau in den Kommentaren Ihres Beitrags hingewiesen hat,

unsigned 5_byte_mask = 0xFFFFFFFFFF; std::cout << *x1 & 5_byte_mask << std::endl;

Sollte eine vernünftige Möglichkeit sein, auf einem kleinen Endian-Computer mit jedem Compiler den gleichen Wert zu erzielen. Es kann nach der einen oder anderen Spezifikation UB sein, aber aus Sicht eines Compilers dereferenzieren Sie acht Bytes an einer gültigen Adresse, von der Sie fünf Bytes initialisiert haben, und maskieren die verbleibenden Bytes, die nicht initialisierte / Junk-Daten sind.

0
okovko 17 Jän. 2019 im 20:54