14

我意识到我正在尝试做的事情并不安全。但我只是在做一些测试和图像处理,所以我的重点是速度。

现在,这段代码为我提供了 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;
};

这也可以,但我仍然需要将所有内容更改为 index c,这有点难看,但如果我真的需要,我可以让它与宏一起使用。

4

3 回答 3

21

(已编辑)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) 但那更难看。您可以通过以下方式解决此问题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 或使用 -pedantic 的 gcc,您将需要(不要将其与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

于 2010-05-20T18:53:28.927 回答
10

为什么不把丑陋的烂摊子变成内联例程呢?就像是:

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

您还可以将此例程作为Pixel, called的成员函数提供,如果您愿意,i()您可以通过它访问值pixel.i()。(当不需要强制执行不变量时,我倾向于将功能与数据结构分开。)

于 2010-05-20T18:50:34.163 回答
10

联合内部结构的问题在于,允许编译器在结构(或类)的成员之间添加填充字节,位字段除外

鉴于:

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还是alphaMSB。通过使用位操作,阅读代码时毫无疑问。

只是我的建议,YMMV。

于 2010-05-20T19:17:48.903 回答