2010-05-20 3 views
11

Я понимаю, что то, что я пытаюсь сделать, небезопасно. Но я просто занимаюсь тестированием и обработкой изображений, поэтому мое внимание здесь сосредоточено на скорости.Использование союзов для упрощения приведения

Прямо сейчас этот код дает мне соответствующие байты для типа значения 32-битного пикселя.

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

Я хотел бы проверить, если у меня есть пиксель, который находится под определенным значением (например, r, g, b <= 0x10). Я решил, что хотел бы просто условно протестировать бит - и битов пикселя с 0x00E0E0E0 (я мог бы иметь неправильную конъюнктуру здесь), чтобы получить темные пиксели.

Вместо того чтобы использовать этот уродливый беспорядок (*((uint32_t*)&pixel)), чтобы получить 32-битное значение без знака Int, я полагал, что должен быть способ для меня, чтобы настроить его так, я могу просто использовать pixel.i, сохраняя при этом возможность ссылаться на зеленый байт используя pixel.g.

Могу ли я это сделать? Это не будет работать:

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

я должен был бы изменить свой существующий код, чтобы сказать pixel.p.g, чтобы получить зеленый цвет байт. То же самое происходит, если я делаю это:

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

Это будет работать тоже, но мне еще нужно, чтобы все изменить, чтобы индексировать в c, который немного некрасиво, но я могу заставить его работать с макросом, если я на самом деле нужно.

+10

Вы редко слышите слова «объединение» и «упрощение» в том же предложении ... –

+4

Я считаю, что применение маски 0xE0 не эквивалентно <= 0x10. В частности, 0x1F больше 0x10, и применение маски даст 0x00 –

+0

И ваш отклоненный «этот уродливый беспорядок» \ * ((uint32_t \ *) & pixel) также нарушит строгие правила псевдонимов. –

ответ

17

(Отредактировано) Оба gcc и MSVC допускают «анонимные» структуры/объединения, которые могут решить вашу проблему. Например:

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

дает (на Intel):

04030201 

Для этого необходимо изменить все объявления о STRUCT Pixel в накидной Pixel в исходном коде. Но этот дефект может быть исправлен с помощью:

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

Это также работает с VC9 с «предупреждением C4201: нестандартное расширение используется: безымянные структуры/объединение». Microsoft использует этот прием, например, в:

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

, но они «обманывают», подавляя нежелательное предупреждение.

Хотя приведенные выше примеры в порядке, если вы слишком часто используете эту технику, вы быстро получите недостижимый код.Пять предложений, чтобы сделать вещи более ясными:

(1) Измените имя bits на что-то уродливое, например union_bits, чтобы четко обозначить что-то необычное.

(2) Вернуться к уродливому гипсе ОП отвергнут, но скрыть свое уродство в макросе или в встроенной функции, как:

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

Но это будет нарушать строгие правила наложения спектров. (См, например, ответ AndreyT в:. C99 strict aliasing rules in C++ (GCC))

(3) Храните Definiton пикселя, но сделать лучше бросок:

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

(4) Но это даже уродливее. Вы можете исправить это с помощью 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 

С VC9, или с помощью GCC -pedantic, вам нужно (не использовать это с Gcc --see примечание в конце) :

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

(5) Возможно, макрос может быть предпочтительным. В GCC, вы можете определить накидной оттенок любого данного типа очень аккуратно:

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

С VC9 и других компиляторов, нет typeof, и могут быть необходимы указатели (не использовать это с НКУ --see примечание в конце):

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

самодокументирован, и безопаснее. И не тоже уродливый. Все эти предложения, скорее всего, будут скомпилированы для идентичного кода, поэтому эффективность не является проблемой. См. Также мой ответ: How to format a function pointer?.

Предупреждение о НКУ: ССАГПЗ Руководство по версии 4.3.4 (но не версия 4.3.0) утверждает, что этот последний пример, с &(x), является неопределенное поведение. См. http://davmac.wordpress.com/2010/01/08/gcc-strict-aliasing-c99/ и http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html.

+0

В этом случае это 'struct', который является анонимным, а не union. – nategoose

+0

@nategoose: Спасибо. Исправлена. –

+0

+1: Никогда не знал, что это возможно! – mmmmmmmm

9

Почему бы не сделать уродливый беспорядок встроенной рутиной? Что-то вроде:

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

Вы также могли бы обеспечить эту процедуру в качестве функции члена для Pixel, называется i(), что позволит вам получить доступ к значению через pixel.i(), если вы предпочитаете, чтобы сделать это таким образом. (Я бы опирался на разделение функциональности из структуры данных, когда инварианты не нуждаются в принудительной реализации.)

+0

-1, даже предлагая макрос, когда любое количество других решений достигнет той же цели без подрыва компилятора. –

+0

@John: достаточно справедливо; исправлено. – fbrereto

+1

'reinterpret_cast' - это C++ - ism. ;) – mipadi

9

Проблема со структурой внутри объединения заключается в том, что компилятору разрешено добавлять прописные байты между членами структуры (или класса), , за исключением полей бит.

Дано:

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

Это может быть выложен как:

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

Таким образом, размер структуры будет 16 байт.

При установке в объединение компилятор будет использовать большую емкость для определения пространства. Кроме того, как вы можете видеть, 32-битное целое не будет правильно выровняться.

Предлагаю создать функции для объединения и извлечения пикселей из 32-разрядного количества. Вы можете объявить его inline тоже:

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

Это намного более надежен, чем структуры внутри союза, в том числе с битовыми полями:

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

Существует еще какая-то тайна, когда читаете это ли red MSB или alpha является MSB. Используя бит-манипуляцию, при чтении кода нет никаких сомнений.

Только мои предложения, YMMV.

Смежные вопросы