c++ - girona - ¿Es un comportamiento legal y bien definido usar una unión para la conversión entre dos estructuras con una secuencia inicial común(ver ejemplo)?
qgis manual (2)
Tengo una API con una estructura A que se enfrenta públicamente y una estructura B interna y necesito poder convertir una estructura B en una estructura A. El código siguiente es un comportamiento legal y bien definido en C99 (y VS 2010 / C89) y C ++ 03 / C ++ 11? Si es así, por favor explique qué lo hace bien definido. Si no lo es, ¿cuál es el medio más eficiente y multiplataforma para convertir entre las dos estructuras?
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;
}
ACTUALIZAR
Simplifiqué el ejemplo y escribí dos aplicaciones diferentes. Una basada en memcpy y la otra utilizando una unión.
Unión:
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;
}
memcpy
#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;
}
Ensamblaje con perfil [DEBUG] (Xcode 6.4, compilador predeterminado de C ++):
Aquí está la diferencia relevante en el ensamblaje para el modo de depuración. Cuando describí las compilaciones de lanzamiento no hubo diferencia en el ensamblaje.
Unión:
movq %rcx, -48(%rbp)
memcpy
movq -40(%rbp), %rsi
movq %rsi, -56(%rbp)
movl -32(%rbp), %edi
movl %edi, -48(%rbp)
Advertencia:
El código de ejemplo basado en la unión produce una advertencia sobre la variable ''a'' que no se utiliza. Como el ensamblaje perfilado es de depuración, no sé si hay algún impacto.
Es legal tanto en C como en C ++.
Por ejemplo, en C99 (6.5.2.3/5) y C11 (6.5.2.3/6):
Se otorga una garantía especial para simplificar el uso de uniones: si una unión contiene varias estructuras que comparten una secuencia inicial común (ver más abajo), y si el objeto de la unión actualmente contiene una de estas estructuras, está permitido inspeccionar la estructura común. Parte inicial de cualquiera de ellos en cualquier lugar que sea visible una declaración del tipo completo de la unión. Dos estructuras comparten una secuencia inicial común si los miembros correspondientes tienen tipos compatibles (y, para campos de bits, los mismos anchos) para una secuencia de uno o más miembros iniciales.
Existen disposiciones similares en C ++ 11 y C ++ 14 (redacción diferente, mismo significado).
Esto está bien, porque los miembros a los que está accediendo son elementos de una secuencia inicial común .
C11 ( 6.5.2.3 Estructura y miembros sindicales ; Semántica ):
[...] si una unión contiene varias estructuras que comparten una secuencia inicial común (ver más abajo), y si el objeto de la unión actualmente contiene una de estas estructuras, está permitido inspeccionar la parte inicial común de cualquiera de ellas en cualquier lugar que La declaración del tipo completado de la unión es visible. Dos estructuras comparten una secuencia inicial común si los miembros correspondientes tienen tipos compatibles (y, para campos de bits, los mismos anchos) para una secuencia de uno o más miembros iniciales.
C ++ 03 ( [class.mem] / 16 ):
Si una unión POD contiene dos o más estructuras POD que comparten una secuencia inicial común, y si el objeto POD-union actualmente contiene una de estas estructuras POD, se permite inspeccionar la parte inicial común de cualquiera de ellas. Dos estructuras POD comparten una secuencia inicial común si los miembros correspondientes tienen tipos compatibles con el diseño (y, para campos de bits, los mismos anchos) para una secuencia de uno o más miembros iniciales.
Otras versiones de los dos estándares tienen un lenguaje similar; desde C ++ 11, la terminología utilizada es de diseño estándar en lugar de POD .
Creo que la confusión puede haber surgido porque C permite el punteo de tipo (alias a un miembro de un tipo diferente) a través de una unión donde C ++ no lo hace; este es el caso principal en el que para garantizar la compatibilidad con C / C ++, tendría que usar memcpy
. Pero en su caso, los elementos a los que está accediendo son del mismo tipo y están precedidos por miembros de tipos compatibles, por lo que la regla de tipificación de tipos no es relevante.