代码之家  ›  专栏  ›  技术社区  ›  Steven Lu

使用联合简化强制转换

  •  12
  • Steven Lu  · 技术社区  · 16 年前

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

    现在,这个代码为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 回复  |  直到 14 年前
        1
  •  18
  •   Community Mohan Dere    9 年前

    ( 编辑 )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 {
            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)返回到丑陋的强制转换拒绝的操作,但在宏或内联函数中隐藏其丑陋,如:

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

    但这将打破严格的别名规则。(例如,参见Andreyt的回答: C99 strict aliasing rules in C++ (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使用-书呆子,你就需要( 不要 用这个 海湾合作委员会 --见末尾注释 ):

    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 ,可能需要指针( 不要 用这个 海湾合作委员会 --见末尾注释 ):

    #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 .

        2
  •  10
  •   Thomas Matthews    16 年前

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

    鉴于:

    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。

        3
  •  10
  •   fbrereto    16 年前

    为什么不把难看的一团糟变成一个在线程序呢?类似:

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

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

    推荐文章