Использование объединений для упрощения трансляций

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

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

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

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

Вместо того, чтобы использовать этот уродливый беспорядок (*((uint32_t*)&pixel)), чтобы получить 32-битное значение unsigned 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, что немного уродливо, но я могу заставить его работать с макросом, если мне действительно нужно.

Ответ 1

(Отредактировано). Как 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: используется нестандартное расширение: nameless struct/union. 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 строгие правила псевдонимов в С++ (GCC).)

(3) Сохраните исходное определение пикселя, но сделайте лучший снимок:

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) является undefined. См. http://davmac.wordpress.com/2010/01/08/gcc-strict-aliasing-c99/ и http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html.

Ответ 2

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

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

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

Ответ 3

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

Дано:

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.