使用联合来简化强制转换

发布于 2024-09-02 21:00:00 字数 821 浏览 4 评论 0原文

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

现在,这段代码为我提供了 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.pg 来获取绿色字节。如果我这样做,也会发生同样的情况:

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

这也可以,但我仍然需要将所有内容更改为索引到 c 中,这有点难看,但如果我真的需要的话,我可以让它与宏一起工作。

I realize that what I am trying to do isn't safe. But I am just doing some testing and image processing so my focus here is on speed.

Right now this code gives me the corresponding bytes for a 32-bit pixel value type.

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

I wanted to check if I have a pixel that is under a certain value (e.g. r, g, b <= 0x10). I figured I wanted to just conditional-test the bit-and of the bits of the pixel with 0x00E0E0E0 (I could have wrong endianness here) to get the dark pixels.

Rather than using this ugly mess (*((uint32_t*)&pixel)) to get the 32-bit unsigned int value, i figured there should be a way for me to set it up so I can just use pixel.i, while keeping the ability to reference the green byte using pixel.g.

Can I do this? This won't work:

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

I would need to edit my existing code to say pixel.p.g to get the green color byte. Same happens if I do this:

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

This would work too but I still need to change everything to index into c, which is a bit ugly but I can make it work with a macro if i really needed to.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

独自←快乐 2024-09-09 21:00:00

已编辑)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:使用非标准扩展:无名结构/联合”。例如,微软使用了这个技巧:

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 或使用 -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,并且可能需要指针(不< /strong> 与 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

(Edited) Both gcc and MSVC allow 'anonymous' structs/unions, which might solve your problem. For example:

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

gives (on Intel):

04030201

This requires changing all your declarations of struct Pixel to union Pixel in your original code. But this defect can be fixed via:

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

This also works with VC9, with 'warning C4201: nonstandard extension used : nameless struct/union'. Microsoft uses this trick, for example, in:

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

but they 'cheat' by suppressing the unwanted warning.

While the above examples are ok, if you use this technique too often, you'll quickly end up with unmaintainable code. Five suggestions to make things clearer:

(1) Change the name bits to something uglier like union_bits, to clearly indicate something out-of-the-ordinary.

(2) Go back to the ugly cast the OP rejected, but hide its ugliness in a macro or in an inline function, as in:

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

But this would break the strict aliasing rules. (See, for example, AndreyT's answer: C99 strict aliasing rules in C++ (GCC).)

(3) Keep the original definiton of Pixel, but do a better cast:

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

(4) But that is even uglier. You can fix this by a 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

With VC9, or with gcc using -pedantic, you'll need (don't use this with gcc--see note at end):

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

(5) A macro may perhaps be preferred. In gcc, you can define a union cast to any given type very neatly:

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

With VC9 and other compilers, there is no typeof, and pointers may be needed (don't use this with gcc--see note at end):

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

Self-documenting, and safer. And not too ugly. All these suggestions are likely to compile to identical code, so efficiency is not an issue. See also my related answer: How to format a function pointer?.

Warning about gcc: The GCC Manual version 4.3.4 (but not version 4.3.0) states that this last example, with &(x), is undefined behaviour. See http://davmac.wordpress.com/2010/01/08/gcc-strict-aliasing-c99/ and http://gcc.gnu.org/ml/gcc/2010-01/msg00013.html.

薄情伤 2024-09-09 21:00:00

联合内部结构的问题是,编译器可以在结构(或类)的成员之间添加填充字节,除了位字段

给定:

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 位数量的像素。您也可以将其声明为内联:

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

阅读此内容是否为红色仍然存在一些谜团是 MSB 或 alpha 是 MSB。通过使用位操作,阅读代码时不会有任何问题。

只是我的建议,YMMV。

The problem with a structure inside a union, is that the compiler is allowed to add padding bytes between members of a structure (or class), except bit fields.

Given:

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

This could be laid out as:

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

So the size of the structure would be 16 bytes.

When put in a union, the compiler would take the larger capacity of the two to determine space. Also, as you can see, a 32 bit integer would not align correctly.

I suggest creating functions to combine and extract pixels from a 32-bit quantity. You can declare it inline too:

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

This is a lot more reliable than a struct inside a union, including one with bit fields:

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

There is still some mystery when reading this whether red is the MSB or alpha is the MSB. By using bit manipulation, there is no question when reading the code.

Just my suggestions, YMMV.

野却迷人 2024-09-09 21:00:00

为什么不把丑陋的混乱变成内联例程呢?类似于:

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

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

Why not make the ugly mess into an inline routine? Something like:

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

You could also provide this routine as a member function for Pixel, called i(), which would allow you to access the value via pixel.i() if you preferred to do it that way. (I'd lean on separating the functionality from the data structure when invariants need not be enforced.)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文