una tipos sobre sindicatos sindicato sindical que pueden preguntas para organizacion oit los libertad las existir empresas empresa cuantos convenio beneficios asociacion c gcc type-conversion unions strict-aliasing

tipos - gcc, aliasing estricto y fundición a través de un sindicato



que es un sindicato (7)

Bueno, es un poco necro-posting, pero aquí hay una historia de terror. Voy a portar un programa que se escribió con la suposición de que el orden de byte nativo es big endian. Ahora también necesito que trabaje en little endian. Desafortunadamente, no puedo usar el orden de bytes nativos en todas partes, ya que se puede acceder a los datos de muchas maneras. Por ejemplo, un entero de 64 bits podría tratarse como dos enteros de 32 bits o como 4 enteros de 16 bits, o incluso como 16 enteros de 4 bits. Para empeorar las cosas, no hay forma de averiguar exactamente qué se almacena en la memoria, porque el software es un intérprete para algún tipo de código de bytes, y los datos están formados por ese código de bytes. Por ejemplo, el código de bytes puede contener instrucciones para escribir una matriz de enteros de 16 bits, y luego acceder a un par de ellos como un flotador de 32 bits. Y no hay forma de predecirlo o alterar el código de bytes.

Por lo tanto, tuve que crear un conjunto de clases de contenedor para trabajar con los valores almacenados en el orden big endian, independientemente de la endianidad nativa. Funcionó perfectamente en Visual Studio y en GCC en Linux sin optimizaciones. Pero con gcc -O2, el infierno se desató. Después de mucha depuración, descubrí que el motivo estaba aquí:

double D; float F; Ul *pF=(Ul*)&F; // Ul is unsigned long *pF=pop0->lu.r(); // r() returns Ul D=(double)F;

Este código se usó para convertir una representación de 32 bits de un flotante almacenado en un entero de 32 bits en el doble. Parece que el compilador decidió hacer la asignación a * pF después de la asignación a D, el resultado fue que la primera vez que se ejecutó el código, el valor de D fue basura y los valores consiguientes fueron "tardíos" en 1 iteración.

Milagrosamente, no hubo otros problemas en ese punto. Así que decidí continuar y probar mi nuevo código en la plataforma original, HP-UX en un procesador RISC con un pedido nativo de big endian. Ahora se rompió de nuevo, esta vez en mi nueva clase:

typedef unsigned long long Ur; // 64-bit uint typedef unsigned char Uc; class BEDoubleRef { double *p; public: inline BEDoubleRef(double *p): p(p) {} inline operator double() { Uc *pu = reinterpret_cast<Uc*>(p); Ur n = (pu[7] & 0xFFULL) | ((pu[6] & 0xFFULL) << 8) | ((pu[5] & 0xFFULL) << 16) | ((pu[4] & 0xFFULL) << 24) | ((pu[3] & 0xFFULL) << 32) | ((pu[2] & 0xFFULL) << 40) | ((pu[1] & 0xFFULL) << 48) | ((pu[0] & 0xFFULL) << 56); return *reinterpret_cast<double*>(&n); } inline BEDoubleRef &operator=(const double &d) { Uc *pc = reinterpret_cast<Uc*>(p); const Ur *pu = reinterpret_cast<const Ur*>(&d); pc[0] = (*pu >> 56) & 0xFFu; pc[1] = (*pu >> 48) & 0xFFu; pc[2] = (*pu >> 40) & 0xFFu; pc[3] = (*pu >> 32) & 0xFFu; pc[4] = (*pu >> 24) & 0xFFu; pc[5] = (*pu >> 16) & 0xFFu; pc[6] = (*pu >> 8) & 0xFFu; pc[7] = *pu & 0xFFu; return *this; } inline BEDoubleRef &operator=(const BEDoubleRef &d) { *p = *d.p; return *this; } };

Por alguna razón realmente extraña, el primer operador de asignación asignó correctamente los bytes 1 a 7. El byte 0 siempre tenía algunas tonterías, lo que rompió todo ya que hay un bit de signo y una parte de orden.

He tratado de utilizar los sindicatos como una solución alternativa:

union { double d; Uc c[8]; } un; Uc *pc = un.c; const Ur *pu = reinterpret_cast<const Ur*>(&d); pc[0] = (*pu >> 56) & 0xFFu; pc[1] = (*pu >> 48) & 0xFFu; pc[2] = (*pu >> 40) & 0xFFu; pc[3] = (*pu >> 32) & 0xFFu; pc[4] = (*pu >> 24) & 0xFFu; pc[5] = (*pu >> 16) & 0xFFu; pc[6] = (*pu >> 8) & 0xFFu; pc[7] = *pu & 0xFFu; *p = un.d;

pero tampoco funcionó. De hecho, fue un poco mejor, solo falló en números negativos.

En este punto, estoy pensando en agregar una prueba simple para la endianidad nativa, y luego hacer todo a través de punteros char* con if (LITTLE_ENDIAN) . Para empeorar las cosas, el programa hace un uso intensivo de los sindicatos en general, lo que parece funcionar bien por ahora, pero después de todo este lío no me sorprendería si se rompe repentinamente sin razón aparente.

¿Tienes alguna historia de terror que contar? El Manual de GCC recientemente agregó una advertencia con respecto a -flexible-aliasing y lanzar un puntero a través de una unión:

[...] Tomar la dirección, arrojar el puntero resultante y desreferenciar el resultado tiene un comportamiento indefinido [énfasis agregado], incluso si el elenco usa un tipo de unión, por ejemplo:

union a_union { int i; double d; }; int f() { double d = 3.0; return ((union a_union *)&d)->i; }

¿Alguien tiene un ejemplo para ilustrar este comportamiento indefinido?

Tenga en cuenta que esta pregunta no se trata de lo que dice el estándar C99, o no dice. Se trata del funcionamiento real de gcc y otros compiladores existentes hoy en día.

Solo estoy adivinando, pero un problema potencial puede estar en el ajuste de d a 3.0. Como d es una variable temporal que nunca se lee directamente, y que nunca se lee mediante un puntero "algo compatible", el compilador puede no molestarse en configurarlo. Y luego f () devolverá algo de basura de la pila.

Mi intento simple e ingenuo fracasa. Por ejemplo:

#include <stdio.h> union a_union { int i; double d; }; int f1(void) { union a_union t; t.d = 3333333.0; return t.i; // gcc manual: ''type-punning is allowed, provided...'' (C90 6.3.2.3) } int f2(void) { double d = 3333333.0; return ((union a_union *)&d)->i; // gcc manual: ''undefined behavior'' } int main(void) { printf("%d/n", f1()); printf("%d/n", f2()); return 0; }

funciona bien, dando en CYGWIN:

-2147483648 -2147483648

Al observar al ensamblador, vemos que gcc optimiza completamente t : f1() simplemente almacena la respuesta precalculada:

movl $-2147483648, %eax

mientras que f2() empuja 3333333.0 en la pila de coma flotante , y luego extrae el valor de retorno:

flds LC0 # LC0: 1246458708 (= 3333333.0) (--> 80 bits) fstpl -8(%ebp) # save in d (64 bits) movl -8(%ebp), %eax # return value (32 bits)

Y las funciones también están en línea (lo que parece ser la causa de algunos errores sutiles de alias estrictos) pero eso no es relevante aquí. (Y este ensamblador no es tan relevante, pero agrega detalles corroborativos).

También tenga en cuenta que tomar direcciones es obviamente incorrecto (o correcto , si está tratando de ilustrar el comportamiento indefinido). Por ejemplo, así como sabemos que esto está mal:

extern void foo(int *, double *); union a_union t; t.d = 3.0; foo(&t.i, &t.d); // undefined behavior

también sabemos que esto está mal:

extern void foo(int *, double *); double d = 3.0; foo(&((union a_union *)&d)->i, &d); // undefined behavior

Para una discusión de antecedentes sobre esto, vea por ejemplo:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1422.pdf
http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html
http://davmac.wordpress.com/2010/02/26/c99-revisited/
http://cellperformance.beyond3d.com/articles/2006/06/understanding-strict-aliasing.html
(= página de búsqueda en Google luego ver página en caché)

¿Cuál es la regla de aliasing estricta?
C99 reglas estrictas de aliasing en C ++ (GCC)

En el primer enlace, borradores de actas de una reunión de ISO hace siete meses, un participante señala en la sección 4.16:

¿Hay alguien que piense que las reglas son lo suficientemente claras? Nadie es realmente capaz de interpretarlos.

Otras notas: Mi prueba fue con gcc 4.3.4, con -O2; opciones -O2 y -O3 implican -fstrict-aliasing. El ejemplo del Manual de GCC asume sizeof (double) > = sizeof (int); no importa si son desiguales.

Además, como lo señala Mike Acton en el enlace cellperformace, -Wstrict-aliasing=2 , pero no =3 , produce una warning: dereferencing type-punned pointer might break strict-aliasing rules para el ejemplo aquí.


El aliasing ocurre cuando el compilador tiene dos punteros diferentes para la misma pieza de memoria. Al encasillar un puntero, está generando un nuevo puntero temporal. Si el optimizador reordena las instrucciones de ensamblaje, por ejemplo, acceder a los dos punteros podría dar dos resultados totalmente diferentes: podría reordenar una lectura antes de escribir en la misma dirección. Es por eso que es un comportamiento indefinido.

Es poco probable que veas el problema en un código de prueba muy simple, pero aparecerá cuando haya mucho en juego.

Creo que la advertencia es dejar en claro que los sindicatos no son un caso especial, a pesar de que es de esperar que lo sean.

Consulte este artículo de Wikipedia para obtener más información sobre aliasing: http://en.wikipedia.org/wiki/Aliasing_(computing)#Conflicts_with_optimization


El hecho de que GCC advierta sobre los sindicatos no significa necesariamente que los sindicatos actualmente no funcionen. Pero aquí hay un ejemplo un poco menos simple que el tuyo:

#include <stdio.h> struct B { int i1; int i2; }; union A { struct B b; double d; }; int main() { double d = 3.0; #ifdef USE_UNION ((union A*)&d)->b.i2 += 0x80000000; #else ((int*)&d)[1] += 0x80000000; #endif printf("%g/n", d); }

Salida:

$ gcc --version gcc (GCC) 4.3.4 20090804 (release) 1 Copyright (C) 2008 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. $ gcc -oalias alias.c -O1 -std=c99 && ./alias -3 $ gcc -oalias alias.c -O3 -std=c99 && ./alias 3 $ gcc -oalias alias.c -O1 -std=c99 -DUSE_UNION && ./alias -3 $ gcc -oalias alias.c -O3 -std=c99 -DUSE_UNION && ./alias -3

Entonces, en GCC 4.3.4, la unión "guarda el día" (asumiendo que quiero la salida "-3"). Deshabilita la optimización que se basa en un alias estricto y que da como resultado la salida "3" en el segundo caso (solamente). Con -Wall, USE_UNION también desactiva la advertencia de tipo de punteo.

No tengo gcc 4.4 para probar, pero por favor, dale un vistazo a este código. Su código en efecto comprueba si la memoria para d se inicializa antes de volver a leerse a través de una unión: la mina prueba si se modificó.

Por cierto, la manera segura de leer la mitad de un doble como int es:

double d = 3; int i; memcpy(&i, &d, sizeof i); return i;

Con la optimización en GCC, esto da como resultado:

int thing() { 401130: 55 push %ebp 401131: 89 e5 mov %esp,%ebp 401133: 83 ec 10 sub $0x10,%esp double d = 3; 401136: d9 05 a8 20 40 00 flds 0x4020a8 40113c: dd 5d f0 fstpl -0x10(%ebp) int i; memcpy(&i, &d, sizeof i); 40113f: 8b 45 f0 mov -0x10(%ebp),%eax return i; } 401142: c9 leave 401143: c3 ret

Entonces no hay una llamada real a memcpy. Si no estás haciendo esto, te mereces lo que obtienes si los sindicatos dejan de trabajar en GCC ;-)



Realmente no entiendo tu problema. El compilador hizo exactamente lo que se suponía que debía hacer en tu ejemplo. La conversión union es lo que hiciste en f1 . En f2 es un tipo de conversión de puntero normal, que lo fundiste en una unión es irrelevante, sigue siendo un casting de puntero


Su afirmación de que el siguiente código es "incorrecto":

extern void foo(int *, double *); union a_union t; t.d = 3.0; foo(&t.i, &t.d); // undefined behavior

... Está Mal. Simplemente tomar la dirección de los dos miembros del sindicato y pasarlos a una función externa no da como resultado un comportamiento indefinido; solo obtienes desreferenciar uno de esos punteros de forma inválida. Por ejemplo, si la función foo regresa inmediatamente sin desreferenciar los punteros que le pasó, entonces el comportamiento no está indefinido. Con una lectura estricta del estándar C99, incluso hay algunos casos en que los punteros se pueden desreferenciar sin invocar un comportamiento indefinido; por ejemplo, podría leer el valor al que hace referencia el segundo puntero, y luego almacenar un valor a través del primer puntero, siempre y cuando ambos apunten a un objeto dinámicamente asignado (es decir, uno sin un "tipo declarado").


Aquí está el mío: en pensar que esto es un error en todos los GCC v5.xy posterior

#include <iostream> #include <complex> #include <pmmintrin.h> template <class Scalar_type, class Vector_type> class simd { public: typedef Vector_type vector_type; typedef Scalar_type scalar_type; typedef union conv_t_union { Vector_type v; Scalar_type s[sizeof(Vector_type) / sizeof(Scalar_type)]; conv_t_union(){}; } conv_t; static inline constexpr int Nsimd(void) { return sizeof(Vector_type) / sizeof(Scalar_type); } Vector_type v; template <class functor> friend inline simd SimdApply(const functor &func, const simd &v) { simd ret; simd::conv_t conv; conv.v = v.v; for (int i = 0; i < simd::Nsimd(); i++) { conv.s[i] = func(conv.s[i]); } ret.v = conv.v; return ret; } }; template <class scalar> struct RealFunctor { scalar operator()(const scalar &a) const { return std::real(a); } }; template <class S, class V> inline simd<S, V> real(const simd<S, V> &r) { return SimdApply(RealFunctor<S>(), r); } typedef simd<std::complex<double>, __m128d> vcomplexd; int main(int argc, char **argv) { vcomplexd a,b; a.v=_mm_set_pd(2.0,1.0); b = real(a); vcomplexd::conv_t conv; conv.v = b.v; for(int i=0;i<vcomplexd::Nsimd();i++){ std::cout << conv.s[i]<<" "; } std::cout << std::endl; }

Debería dar

c010200:~ peterboyle$ g++-mp-5 Gcc-test.cc -std=c++11 c010200:~ peterboyle$ ./a.out (1,0)

Pero bajo -O3: CREO QUE ESTO ES INCORRECTO Y UN ERROR DE COMPILADOR

c010200:~ peterboyle$ g++-mp-5 Gcc-test.cc -std=c++11 -O3 c010200:~ peterboyle$ ./a.out (0,0)

Bajo g ++ 4.9

c010200:~ peterboyle$ g++-4.9 Gcc-test.cc -std=c++11 -O3 c010200:~ peterboyle$ ./a.out (1,0)

Bajo llvm xcode

c010200:~ peterboyle$ g++ Gcc-test.cc -std=c++11 -O3 c010200:~ peterboyle$ ./a.out (1,0)