2010-05-20 18 views
11

Mi rendo conto che ciò che sto cercando di fare non è sicuro. Ma sto solo facendo alcuni test e l'elaborazione delle immagini quindi il mio obiettivo qui è sulla velocità.Uso dei sindacati per semplificare i cast

In questo momento questo codice mi fornisce i byte corrispondenti per un tipo di valore di pixel a 32 bit.

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

ho voluto verificare se ho un pixel che è sotto un certo valore (ad esempio r, g, b <= 0x10). Ho pensato che volevo solo testare il bit e il bit del pixel con 0x00E0E0E0 (potrei avere un endianness sbagliato qui) per ottenere i pixel scuri.

Piuttosto che utilizzare questo pasticcio sgradevole (*((uint32_t*)&pixel)) per ottenere il valore int non firmato a 32 bit, ho pensato che dovrebbe esserci un modo per configurarlo in modo da poter usare semplicemente pixel.i, pur mantenendo la possibilità di fare riferimento al byte verde utilizzando pixel.g.

Posso fare questo? Questo non funziona:

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

avrei bisogno di modificare il mio codice esistente a dire pixel.p.g per ottenere il byte di colore verde. Lo stesso accade se faccio questo:

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

Ciò funzionerebbe anche, ma ho ancora bisogno di cambiare tutto per indicizzare c, che è un po 'brutto, ma posso farlo funzionare con una macro se ho davvero bisogno di.

+10

Raramente si sentono le parole "unione" e "semplificazione" nella stessa frase ... –

+4

Credo che applicare la maschera 0xE0 non sia equivalente a <= 0x10. In particolare 0x1F è maggiore di 0x10 e l'applicazione della maschera produrrà 0x00 –

+0

E il tuo rifiuto 'questo brutto pasticcio' \ * ((uint32_t \ *) e pixel) infrangerebbe anche le rigide regole di aliasing. –

risposta

17

(Modificato) Sia gcc che MSVC consentono di creare strutture/unioni "anonime", che potrebbero risolvere il problema. Per esempio:

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

dà (su Intel):

04030201 

Ciò richiede la modifica tutte le dichiarazioni di struct Pixel a Pixel unione nel codice originale. Ma questo difetto può essere fissato tramite:

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

Questo funziona anche con VC9, con 'C4201 di avviso: estensione non standard utilizzata: senza nome struct/union'. Microsoft utilizza questo trucco, per esempio, in:

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

ma 'imbrogliare' sopprimendo l'avvertimento indesiderato.

Mentre gli esempi precedenti sono ok, se si utilizza questa tecnica troppo spesso, si finisce rapidamente con il codice non gestibile.Cinque suggerimenti per rendere le cose più chiare:

(1) Modificare il nome bits in qualcosa di più brutto come union_bits, per indicare chiaramente qualcosa di straordinario.

(2) Torna il brutto cast OP ha respinto, ma nascondere la sua bruttezza in una macro o in una funzione inline, come in:

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

Ma questo avrebbe rotto le rigide regole di aliasing. (Si veda, ad esempio, la risposta di AndreyT:. C99 strict aliasing rules in C++ (GCC))

(3) Tenere il definiton originale di Pixel, ma fare un cast meglio:

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

(4) Ma questo è anche più brutto. È possibile risolvere questo problema da 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 utilizzando -pedantic, è necessario (non uso questo con gcc --see la nota alla fine) :

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

(5) Può essere preferibile una macro. In gcc, è possibile definire un cast sindacato per un determinato tipo molto ordinatamente:

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

Con VC9 e altri compilatori, non c'è typeof, e puntatori potrebbe essere necessario (no uso questo con gcc nota --see alla fine):

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

auto-documentazione, e più sicuro. E non anche brutto. È probabile che tutti questi suggerimenti vengano compilati con codice identico, quindi l'efficienza non è un problema. Vedi anche la mia risposta correlata: How to format a function pointer?.

Attenzione riguardo gcc: Manuale Il GCC versione 4.3.4 (ma non versione 4.3.0) afferma che quest'ultimo esempio, con &(x), è comportamento indefinito. Vedi http://davmac.wordpress.com/2010/01/08/gcc-strict-aliasing-c99/ e http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html.

+0

In questo caso è la 'struct' che è anonima, non l'unione. – nategoose

+0

@nategoose: Grazie. Correzione fatta. –

+0

+1: non si sa mai che è possibile! – mmmmmmmm

9

Perché non trasformare il brutto pasticcio in una routine in linea? Qualcosa di simile:

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

Si potrebbe anche fornire questa routine come una funzione di membro per Pixel, chiamato i(), che permetterebbe di accedere al valore attraverso pixel.i() se avremmo preferito farlo in quel modo. (Mi piacerebbe separare la funzionalità dalla struttura dati quando non è necessario imporre invarianti.)

+0

-1 anche per suggerire una macro quando un numero qualsiasi di altre soluzioni raggiungerebbe lo stesso obiettivo senza sovvertire il compilatore. –

+0

@ John: abbastanza giusto; fisso. – fbrereto

+1

'reinterpret_cast' è un C++ - ism. ;) – mipadi

9

Il problema con una struttura interna un'unione, è che il compilatore è permesso di aggiungere byte di riempimento tra membri di una struttura (o classe), tranne campi di bit.

Dato:

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

Questo potrebbe essere presentata come:

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

Quindi la dimensione della struttura sarebbe 16 byte.

Quando si inserisce un unione, il compilatore prenderà la maggiore capacità dei due per determinare lo spazio. Inoltre, come puoi vedere, un intero a 32 bit non si allinea correttamente.

Suggerisco di creare funzioni per combinare ed estrarre pixel da una quantità a 32 bit. Si può dichiarare inline troppo:

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; 
} 

Questo è molto più affidabile di una struttura all'interno di un sindacato, tra cui uno con campi di bit:

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

C'è ancora qualche mistero, quando la lettura di questo se red è l'MSB o alpha è l'MSB. Usando la manipolazione di bit, non c'è dubbio quando si legge il codice.

Solo i miei suggerimenti, YMMV.

Problemi correlati