2010-05-20 10 views
11

Me doy cuenta de que lo que estoy tratando de hacer no es seguro. Pero solo estoy haciendo algunas pruebas y procesando imágenes, así que mi enfoque aquí es la velocidad.Usar uniones para simplificar moldes

Ahora este código me da los bytes correspondientes para un tipo de valor de píxel de 32 bits.

struct Pixel { 
    unsigned char b,g,r,a; 
}; 

quería comprobar si tengo un píxel que se encuentra bajo un cierto valor (por ejemplo r, g, b <= 0x10). Pensé que quería probar condicionalmente el bit y los bits del píxel con 0x00E0E0E0 (podría tener endiancias erróneas aquí) para obtener los píxeles oscuros.

En lugar de utilizar este horrible desastre (*((uint32_t*)&pixel)) para obtener el valor entero sin signo de 32 bits, pensé que debería haber una manera para mí para configurarlo de manera que sólo puede utilizar pixel.i, mientras se mantiene la capacidad de hacer referencia al byte verde usando pixel.g.

¿Puedo hacer esto? Esto no funcionará:

struct Pixel { 
    unsigned char b,g,r,a; 
}; 
union Pixel_u { 
    Pixel p; 
    uint32_t bits; 
}; 

que tendría que modificar mi código existente decir pixel.p.g para obtener el byte de color verde. Lo mismo sucede si hago esto:

union Pixel { 
    unsigned char c[4]; 
    uint32_t bits; 
}; 

Esto funcionaría también, pero todavía tengo que cambiar todo para indexar en c, que es un poco feo, pero puedo hacer que funcione con una macro si realmente necesitaba.

+10

Raramente escuchas las palabras "unión" y "simplificar" en la misma oración ... –

+4

Creo que aplicar la máscara 0xE0 no es equivalente a <= 0x10. En particular, 0x1F es mayor que 0x10 y aplicar la máscara arrojará 0x00 –

+0

Y su rechazado 'este feo lío' \ * ((uint32_t \ *) & pixel) también rompería las reglas estrictas de aliasing. –

Respuesta

17

(Editado) Tanto gcc y MSVC permiten 'anónimos' structs/sindicatos, que podría resolver su problema. Por ejemplo:

union Pixel { 
    struct {unsigned char b,g,r,a;}; 
    uint32_t bits; // use 'unsigned' for MSVC 
} 

foo.b = 1; 
foo.g = 2; 
foo.r = 3; 
foo.a = 4; 
printf ("%08x\n", foo.bits); 

da (en Intel):

04030201 

Esto requiere cambiar todas sus declaraciones de Pixel estructura a Pixel unión en su código original. Pero este defecto se puede fijar a través de:

struct Pixel { 
    union { 
     struct {unsigned char b,g,r,a;}; 
     uint32_t bits; 
    }; 
} foo; 

foo.b = 1; 
foo.g = 2; 
foo.r = 3; 
foo.a = 4; 
printf ("%08x\n", foo.bits); 

Esto también funciona con VC9, la 'C4201 advertencia: la extensión no estándar utilizado: sin nombre struct/union'. Microsoft utiliza este truco, por ejemplo, en:

typedef union { 
    struct { 
     DWORD LowPart; 
     LONG HighPart; 
    }; // <-- nameless member! 
    struct { 
     DWORD LowPart; 
     LONG HighPart; 
    } u; 
    LONGLONG QuadPart; 
} LARGE_INTEGER; 

pero 'engañar' al suprimir la advertencia no deseado.

Si bien los ejemplos anteriores son correctos, si utiliza esta técnica con demasiada frecuencia, terminará rápidamente con un código que no se puede mantener.Cinco sugerencias para aclarar las cosas:

(1) Cambie el nombre bits a algo más feo como union_bits, para indicar claramente algo fuera de lo común.

(2) Volver a la fea fundido el OP rechazada, pero ocultar su fealdad en una macro o en una función en línea, como en:

#define BITS(x) (*(uint32_t*)&(x)) 

Pero esto rompería las estrictas reglas de alias. (Véase, por ejemplo, la respuesta de AndreyT:. C99 strict aliasing rules in C++ (GCC))

(3) Mantenga el definiton original de píxeles, pero hacer un mejor reparto:

struct Pixel {unsigned char b,g,r,a;} foo; 
// ... 
printf("%08x\n", ((union {struct Pixel dummy; uint32_t bits;})foo).bits); 

(4) Pero eso es aún más feo. Puede solucionar este problema mediante un typedef:

struct Pixel {unsigned char b,g,r,a;} foo; 
typedef union {struct Pixel dummy; uint32_t bits;} CastPixelToBits; 
// ... 
printf("%08x\n", ((CastPixelToBits)foo).bits); // not VC9 

Con VC9, o con gcc usando -pedantic, necesitará (no hacer uso esto con gcc --ver nota al final) :

printf("%08x\n", ((CastPixelToBits*)&foo)->bits); // VC9 (not gcc) 

(5) Una macro quizás sea la preferida. En gcc, se puede definir un elenco unión a cualquier tipo dado muy claramente:

#define CAST(type, x) (((union {typeof(x) src; type dst;})(x)).dst) // gcc 
// ... 
printf("%08x\n", CAST(uint32_t, foo)); 

Con VC9 y otros compiladores, no hay typeof, y pueden ser necesarios los punteros (no lo hacen uso esto con gcc nota --ver al final):

#define CAST(typeof_x, type, x) (((union {typeof_x src; type dst;}*)&(x))->dst) 

auto-documentado, y más seguro. Y no también feo. Es probable que todas estas sugerencias se compilen con un código idéntico, por lo que la eficiencia no es un problema. Ver también mi respuesta relacionada: How to format a function pointer?.

Advertencia sobre gcc: Manual El GCC versión 4.3.4 (pero no versión 4.3.0) establece que este último ejemplo, con &(x), es un comportamiento indefinido . Ver http://davmac.wordpress.com/2010/01/08/gcc-strict-aliasing-c99/ y http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html.

+0

En ese caso, es la 'struct' que es anónima, no la unión. – nategoose

+0

@nategoose: Gracias. Corrección hecha. –

+0

+1: ¡Nunca supe que esto es posible! – mmmmmmmm

9

¿Por qué no hacer el desastre feo en una rutina en línea? Algo así como:

inline uint32_t pixel32(const Pixel& p) 
{ 
    return *reinterpret_cast<uint32_t*>(&p); 
} 

También podría proporcionar esta rutina como una función miembro de Pixel, llamado i(), lo que permitirá acceder a través del valor pixel.i() si preferido hacerlo de esa manera. (Me apoyo en la separación de la funcionalidad de la estructura de datos cuando invariantes no tienen que ser cumplidas.)

+0

-1 por incluso sugerir una macro cuando cualquier cantidad de otras soluciones lograría el mismo objetivo sin subvertir el compilador. –

+0

@John: Bastante justo; fijo. – fbrereto

+1

'reinterpret_cast' es un C++ - ismo. ;) – mipadi

9

El problema con una estructura dentro de una unión, es que el compilador puede agregar bytes de relleno entre miembros de una estructura (o clase), excepto los campos de bit.

dado:

struct Pixel 
{ 
    unsigned char red; 
    unsigned char green; 
    unsigned char blue; 
    unsigned char alpha; 
}; 

Esto podría ser establecidos como:

Offset Field 
------ ----- 
0x00 red 
0x04 green 
0x08 blue 
0x0C alpha 

así que el tamaño de la estructura sería de 16 bytes.

Cuando se coloca en una unión, el compilador tomaría la mayor capacidad de los dos para determinar el espacio. Además, como puede ver, un entero de 32 bits no se alinearía correctamente.

Sugiero crear funciones para combinar y extraer píxeles de una cantidad de 32 bits. Se puede declarar que inline también:

void Int_To_Pixel(const unsigned int word, 
        Pixel& p) 
{ 
    p.red = (word & 0xff000000) >> 24; 
    p.blue = (word & 0x00ff0000) >> 16; 
    p.green = (word & 0x0000ff00) >> 8; 
    p.alpha = (word & 0x000000ff); 
    return; 
} 

Esto es mucho más fiable que una estructura dentro de una unión, incluyendo uno con campos de bits:

struct Pixel_Bit_Fields 
{ 
    unsigned int red::8; 
    unsigned int green::8; 
    unsigned int blue::8; 
    unsigned int alpha::8; 
}; 

Todavía hay algo de misterio al leer este si red es el MSB o alpha es el MSB. Al usar la manipulación de bits, no hay duda al leer el código.

Solo mis sugerencias, YMMV.

Cuestiones relacionadas