Использование союзов для упрощения приведений - PullRequest
12 голосов
/ 20 мая 2010

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

Прямо сейчас этот код дает мне соответствующие байты для 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, что немного уродливо, но я могу заставить его работать с макросом, если мне действительно нужно.

Ответы [ 3 ]

18 голосов
/ 20 мая 2010

( Edited ) И 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 на union 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) Вернитесь к уродливому приведению отвергнутого OP, но скройте его уродство в макросе или встроенной функции, например:

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

Но это нарушит строгие правила наложения имен. (См., Например, ответ AndreyT: C99 строгие правила алиасинга в C ++ (GCC) .)

(3) Сохраните первоначальное определение Pixel, но сделайте лучший бросок:

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 - см. Примечание в конце ) :

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, и могут потребоваться указатели ( не используйте это с gcc - см. Примечание в конце ):

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

Самодокументируемый и безопасный. И не слишком безобразно. Все эти предложения, вероятно, скомпилированы в идентичный код, поэтому эффективность не является проблемой. Смотрите также мой связанный ответ: Как отформатировать указатель функции? .

Предупреждение о gcc: В Руководстве GCC версии 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.

10 голосов
/ 20 мая 2010

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

Дано:

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.

10 голосов
/ 20 мая 2010

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

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

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

...