我意识到我要做的事情并不安全。但我只是在做一些测试和图像处理,所以我的重点是速度。
现在这段代码为我提供了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
,这有点难看,但如果我真的需要,我可以使用宏。
答案 0 :(得分:18)
(已编辑)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);
给出(在英特尔):
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 strict aliasing rules in 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)但那甚至是 uglier 。您可以通过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)
自我记录,更安全。并非太丑陋。所有这些建议都可能编译成相同的代码,因此效率不是问题。另请参阅我的相关答案:How to format a function pointer?。
关于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。
答案 1 :(得分:10)
为什么不将丑陋的混乱变成内联例程呢?类似的东西:
inline uint32_t pixel32(const Pixel& p)
{
return *reinterpret_cast<uint32_t*>(&p);
}
您还可以将此例程作为Pixel
的成员函数提供,称为i()
,如果您愿意这样做,则可以通过pixel.i()
访问该值。 (当不需要强制实施不变量时,我会依靠将功能与数据结构分开。)
答案 2 :(得分:10)
union内部结构的问题是,允许编译器在结构(或类)的成员之间添加填充字节,除了位字段。
假设:
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。