Ich habe eine API mit einer öffentlich zugänglichen Struktur A und einer internen Struktur B und muss in der Lage sein, eine Struktur B in eine Struktur A umzuwandeln. Ist der folgende Code zulässig und gut definiertes Verhalten in C99 (und VS 2010/C89) und C++03/C++11? Wenn ja, erklären Sie bitte, was es wohldefiniert macht. Wenn dies nicht der Fall ist, was ist das effizienteste und plattformübergreifendste Mittel zum Konvertieren zwischen den beiden Strukturen?
struct A {
uint32_t x;
uint32_t y;
uint32_t z;
};
struct B {
uint32_t x;
uint32_t y;
uint32_t z;
uint64_t c;
};
union U {
struct A a;
struct B b;
};
int main(int argc, char* argv[]) {
U u;
u.b.x = 1;
u.b.y = 2;
u.b.z = 3;
u.b.c = 64;
/* Is it legal and well defined behavior when accessing the non-write member of a union in this case? */
DoSomething(u.a.x, u.a.y, u.a.z);
return 0;
}
AKTUALISIEREN
Ich habe das Beispiel vereinfacht und zwei verschiedene Anwendungen geschrieben. Einer basiert auf Memcpy und der andere auf Union.
Union:
struct A {
int x;
int y;
int z;
};
struct B {
int x;
int y;
int z;
long c;
};
union U {
struct A a;
struct B b;
};
int main(int argc, char* argv[]) {
U u;
u.b.x = 1;
u.b.y = 2;
u.b.z = 3;
u.b.c = 64;
const A* a = &u.a;
return 0;
}
Speicher:
#include <string.h>
struct A {
int x;
int y;
int z;
};
struct B {
int x;
int y;
int z;
long c;
};
int main(int argc, char* argv[]) {
B b;
b.x = 1;
b.y = 2;
b.z = 3;
b.c = 64;
A a;
memcpy(&a, &b, sizeof(a));
return 0;
}
Profilierte Montage [DEBUG] (Xcode 6.4, Standard-C++-Compiler):
Hier ist der relevante Unterschied in der Assembly für den Debug-Modus. Als ich die Release-Builds profilierte, gab es keinen Unterschied in der Montage.
Union:
movq %rcx, -48(%rbp)
Speicher:
movq -40(%rbp), %rsi
movq %rsi, -56(%rbp)
movl -32(%rbp), %edi
movl %edi, -48(%rbp)
Vorbehalt:
Der auf Union basierende Beispielcode erzeugt eine Warnung, dass die Variable „a“ nicht verwendet wird. Da die profilierte Assembly aus dem Debug stammt, weiß ich nicht, ob es Auswirkungen gibt.
Das ist in Ordnung, da die Mitglieder, auf die Sie zugreifen, Elemente von a sind gemeinsame Anfangssequenz.
C11 (6.5.2.3 Struktur und Gewerkschaftsmitglieder; Semantik):
[…] Wenn eine Union mehrere Strukturen enthält, die eine gemeinsame Anfangssequenz haben (siehe unten), und wenn das Union-Objekt derzeit eine dieser Strukturen enthält, ist es zulässig, den gemeinsamen Anfangsteil einer von ihnen überall dort zu untersuchen, wo eine Deklaration des abgeschlossenen Typs ist des Verbandes sichtbar. Zwei Strukturen teilen sich a gemeinsame Anfangssequenz wenn entsprechende Mitglieder kompatible Typen (und für Bitfelder dieselben Breiten) für eine Folge von einem oder mehreren Anfangsmitgliedern haben.
C++03 ([class.mem]/16):
Wenn eine POD-Vereinigung zwei oder mehr POD-Strukturen enthält, die eine gemeinsame Anfangssequenz teilen, und wenn das POD-Vereinigungsobjekt derzeit eine dieser POD-Strukturen enthält, ist es erlaubt, den gemeinsamen Anfangsteil von jeder von ihnen zu untersuchen. Zwei POD-Strukturen teilen sich eine gemeinsame Anfangssequenz, wenn entsprechende Mitglieder Layout-kompatible Typen (und für Bitfelder dieselben Breiten) für eine Sequenz von einem oder mehreren Anfangsmitgliedern haben.
Andere Versionen der beiden Standards haben eine ähnliche Sprache; seit C++11 ist die verwendete Terminologie Standardlayout statt POD.
Ich denke, die Verwirrung ist möglicherweise entstanden, weil C dies zulässt Wortspiel (Aliasing eines Members eines anderen Typs) über eine Union, wo C++ dies nicht tut; Dies ist der Hauptfall, in dem Sie die C/C++-Kompatibilität sicherstellen müssten memcpy
. Aber in Ihrem Fall haben die Elemente, auf die Sie zugreifen, die gleich Typ und ihnen sind Member kompatibler Typen vorangestellt, sodass die Typ-Punning-Regel nicht relevant ist.
Es ist sowohl in C als auch in C++ legal
Zum Beispiel in C99 (6.5.2.3/5) und C11 (6.5.2.3/6):
Um die Verwendung von Unions zu vereinfachen, wird eine besondere Garantie gegeben: Wenn eine Union mehrere Strukturen enthält, die eine gemeinsame Anfangssequenz teilen (siehe unten), und wenn das Union-Objekt derzeit eine dieser Strukturen enthält, ist es erlaubt, die gemeinsame zu inspizieren Anfangsteil einer von ihnen überall dort, wo eine Deklaration des vollständigen Typs der Vereinigung sichtbar ist. Zwei Strukturen teilen sich eine gemeinsame Anfangssequenz, wenn entsprechende Mitglieder kompatible Typen (und für Bitfelder dieselben Breiten) für eine Sequenz von einem oder mehreren Anfangsmitgliedern haben.
Ähnliche Bestimmungen gibt es in C++11 und C++14 (unterschiedlicher Wortlaut, gleiche Bedeutung).
Ich glaube nicht, dass das sogar kompilieren wird.
struct C
ist ein unvollständiger Typ. Was machtstruct B
ein unvollständiger Typ. Was machtunion U
ein unvollständiger Typ. Es gibt keine Möglichkeit (AFAIK), dass Sie U / B aufdecken und C so verstecken können. Sie können nur Zeiger auf unvollständige Typen haben.– Kaylum
22. Juli 2015 um 3:43 Uhr
Ich schätze, Sie werden ziemlich viele theoretische Antworten wie “undefiniertes Verhalten in C++”, “gültig in C99” usw. erhalten. Ich bin mir jedoch ziemlich sicher, dass Ihr Code in der Praxis korrekt funktioniert, ich sehe keinen Grund dafür Compiler, um dies zu schrauben.
– stgatilow
22. Juli 2015 um 4:23 Uhr
@stgatilov: Ich kann mir vorstellen, dass Optimierungen dies brechen, es sei denn, Sie deklarieren die Vereinigung von
volatile struct...
– Änderungen an einer Struktur werden in Registern zwischengespeichert, nicht ins RAM gespült, dann werden die (alten) RAM-Werte durch die “andere Seite” gelesen.– SF.
22. Juli 2015 um 10:52 Uhr
@SF. Basierend darauf, wie der Standard “Vereinigung” definiert, ist das kein Problem. Außerdem macht das Deklarieren eines Typs als „flüchtig“ ihn nicht Thread-sicher oder atomar.
– Kodierer
23. Juli 2015 um 0:23 Uhr
@SF. Ich habe darauf hingewiesen, dass Ihr Verständnis der Funktionsweise des flüchtigen Modifikators falsch ist, da ich nicht möchte, dass andere Leser durch Ihre Kommentare verwirrt werden.
– Kodierer
23. Juli 2015 um 10:52 Uhr