Ist es legal und gut definiert, eine Union für die Konvertierung zwischen zwei Strukturen mit einer gemeinsamen Anfangssequenz zu verwenden (siehe Beispiel)?

Lesezeit: 6 Minuten

Benutzer-Avatar
Kodierer

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.

  • Ich glaube nicht, dass das sogar kompilieren wird. struct C ist ein unvollständiger Typ. Was macht struct B ein unvollständiger Typ. Was macht union 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

Benutzer-Avatar
Ekatmur

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.

  • Können Sie Links zu Ihren Quellen angeben?

    – Kodierer

    22. Juli 2015 um 8:37 Uhr

  • @Codorilla siehe stackoverflow.com/questions/81656/…

    – ekatmur

    22. Juli 2015 um 8:39 Uhr

  • @Codorilla persönlich würde ich Memcpy für den Fall reservieren, in dem Sie zwischen Mitgliedern verschiedener Typen konvertieren. Ich wäre überrascht, wenn memcpy in Code verwendet wird, in dem die Union-Methode zulässig ist. Wie Sie sagen, kann ein optimierender Compiler den Memcpy “durchschauen”, aber Sie führen nicht immer optimierten Code aus – er würde beim Durchlaufen eines Debug-Builds im Weg stehen.

    – ekatmur

    22. Juli 2015 um 8:51 Uhr

  • Es gibt auch das Problem, dass das Verhalten von union gut definiert ist, während seine Implementierung definiert, ob die Verwendung von memcpy so effizient ist. Die profilierte Assembly im Debug-Modus macht das deutlich. Die Verwendung von union bietet in diesem Fall plattformübergreifend stärkere Leistungsgarantien.

    – Kodierer

    22. Juli 2015 um 8:55 Uhr


  • @underscore_d Ich könnte mir vorstellen, dass es eine aggressivere Optimierung erlaubt; C kann diese Funktionsargumente annehmen S* s und T* t kein Alias, auch wenn sie eine gemeinsame Anfangssequenz haben, solange no union { S; T; } in Sicht ist, während C++ diese Annahme nur zur Verbindungszeit treffen kann. Es könnte sich lohnen, eine separate Frage zu diesem Unterschied zu stellen.

    – ekatmur

    5. Januar 2016 um 15:17 Uhr

Benutzer-Avatar
n. 1.8e9-wo-ist-meine-Aktie m.

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).

  • – obwohl C++ – vielleicht bezeichnenderweise – nicht die Vorgabe macht, dass “eine Deklaration des vollständigen Typs der Union [must be] sichtbar”

    – Unterstrich_d

    5. Januar 2016 um 15:35 Uhr

  • @underscore_d: Und gcc würdigt eine solche Garantie nicht mit Zeigern auf die Mitgliedstypen der Vereinigungen, selbst wenn die vollständige Vereinigungsdeklaration sichtbar ist.

    – Superkatze

    6. September 2016 um 22:02 Uhr


1103820cookie-checkIst es legal und gut definiert, eine Union für die Konvertierung zwischen zwei Strukturen mit einer gemeinsamen Anfangssequenz zu verwenden (siehe Beispiel)?

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

Privacy policy