2010-05-20 17 views
11

Zdaję sobie sprawę, że to, co próbuję zrobić, nie jest bezpieczne. Ale robię tylko testowanie i przetwarzanie obrazu, więc skupiam się tutaj na szybkości.Używanie związków w celu uproszczenia obsady

Teraz ten kod podaje mi odpowiednie bajty dla 32-bitowego typu wartości pikseli.

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

Chciałem sprawdzić, czy mam piksel, który jest poniżej pewnej wartości (np r, g, b <= 0x10). Pomyślałem, że chcę tylko przetestować bit i bitów piksela za pomocą 0x00E0E0E0 (mógłbym mieć tutaj błędną endianię), aby uzyskać ciemne piksele.

Zamiast używać tego brzydkiego bałagan (*((uint32_t*)&pixel)), aby uzyskać 32-bit unsigned int wartość, pomyślałem nie powinno być dla mnie sposób skonfigurować go tak, mogę po prostu użyć pixel.i, zachowując zdolność do odwoływania zieloną bajt przy użyciu pixel.g.

Czy mogę to zrobić? To nie będzie działać:

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

musiałbym edytować istniejący kod powiedzieć pixel.p.g aby uzyskać zielony kolor bajt. To samo dzieje się, jeśli mogę to zrobić:

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

to będzie działać zbyt ale nadal trzeba zmienić wszystko do indeksu do c, który jest nieco brzydki, ale mogę pracować z makro czy naprawdę potrzebne.

+10

Rzadko słyszysz słowa "union" i "upraszczaj" w tym samym zdaniu ... –

+4

Uważam, że stosowanie maski 0xE0 nie jest równoważne <= 0x10. W szczególności 0x1F jest większe niż 0x10 i zastosowanie maski da 0x00 –

+0

A twój odrzucony "ten paskudny bałagan" \ * ((uint32_t \ *) i piksel) również złamie zasady ścisłego aliasingu. –

Odpowiedz

17

(Zmieniano) Zarówno GCC i MSVC pozwalają „Anonymous” kodowanym/związki, które mogłyby rozwiązać problem. Na przykład:

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

daje (na Intel):

04030201 

Wymaga to zmienia wszystkie swoje deklaracje z struct Pixel do związkowej Pixel w oryginalnym kodzie. Ale ta wada może być ustalona poprzez:

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

ta współpracuje również z VC9, z „C4201 ostrzeżenie: rozszerzenia niestandardowe zastosowanie: bezimienny struct/union”. Microsoft wykorzystuje tę sztuczkę, na przykład w:

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

, ale one "oszukują", tłumiąc niechciane ostrzeżenie.

Chociaż powyższe przykłady są w porządku, jeśli użyjesz tej techniki zbyt często, szybko otrzymasz niezmienny kod.Pięć wskazówek, aby dokonać rzeczy jaśniej:

(1) Zmień nazwę bits coś brzydsze jak union_bits, aby wyraźnie wskazać coś out-of-the-zwyczajne.

(2) Wróć do brzydkiego obsady PO odrzucony, ale ukryć swoją brzydotę w makro lub w funkcji inline, jak w:

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

Ale to byłoby złamać surowe zasady aliasingu. (Patrz, na przykład, odpowiedź AndreyT za:. C99 strict aliasing rules in C++ (GCC))

(3) Zachowaj oryginalne zaliczane do Pixel, ale do lepszej obsady:

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

(4) Ale to jeszcze brzydsze. Można to naprawić przez 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 

Z VC9 lub z gcc za pomocą -pedantic, musisz (nie wykorzystanie tego z gcc --see uwaga na koniec) :

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

(5) Być może preferowane może być makro. W gcc, można zdefiniować obsady Unię do danego rodzaju bardzo starannie:

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

Z VC9 i innych kompilatorów, nie ma typeof i może być potrzebne wskaźniki (nie użyć tego z gcc --see uwaga na koniec):

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

samodokumentujące i bezpieczniejsze. I nie brzydko . Wszystkie te sugestie będą prawdopodobnie kompilować do identycznego kodu, więc efektywność nie jest problemem. Zobacz także moją powiązaną odpowiedź: How to format a function pointer?.

Ostrzeżenie o gcc: GCC ręczny Wersja 4.3.4 (ale nie wersja 4.3.0) stwierdza, że ​​ten ostatni przykład, z &(x) jest niezdefiniowane zachowanie. Zobacz http://davmac.wordpress.com/2010/01/08/gcc-strict-aliasing-c99/ i http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html.

+0

W tym przypadku jest to "struktura", która jest anonimowa, a nie związek. – nategoose

+0

@nategoose: Dziękuję. Korekta wykonana. –

+0

+1: Nigdy nie wiedziałem, że to możliwe! – mmmmmmmm

9

Dlaczego nie sprawić, że brzydki bałagan stanie się procedurą inline? Coś jak:

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

Można również dostarczyć tę procedurę jako funkcji składowej dla Pixel, zwany i(), które pozwalają na dostęp do wartości poprzez pixel.i() jeśli wolał zrobić to w ten sposób. (Chciałbym oprzeć się na oddzielenie funkcji ze struktury danych, gdy niezmienniki nie muszą być egzekwowane.)

+0

-1 nawet sugerując makro, gdy dowolna liczba innych rozwiązań osiągnąłaby ten sam cel bez podważania kompilatora. –

+0

@John: Uczciwa; naprawiony. – fbrereto

+1

'reinterpret_cast' jest C++ - ism. ;) – mipadi

9

Problem ze strukturą wewnątrz Unii, jest to, że kompilator może dodać bajty wypełniające pomiędzy członkami struktury (lub klasy), wyjątkiem bitowych pól.

Dane:

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

To może być określone jako:

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

więc rozmiar struktury byłoby 16 bajtów.

Po umieszczeniu w złączu kompilator potrzebowałby większej pojemności dla określenia przestrzeni. Ponadto, jak widać, 32-bitowa liczba całkowita nie wyrówna się poprawnie.

Sugeruję tworzenie funkcji łączenia i wyodrębniania pikseli z 32-bitowej ilości. Można zadeklarować inline też:

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

To jest dużo bardziej niezawodny niż struct wewnątrz Unii, w tym jednego z pól bitowych:

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

Jest jeszcze jakaś tajemnica podczas czytania tego, czy red jest MSB lub alpha jest MSB. Używając manipulacji bitem, nie ma wątpliwości podczas czytania kodu.

Po prostu moje sugestie, YMMV.

Powiązane problemy