【问题标题】:How to assign a 32-bit unsigned integer to a bit field containing 32 bits如何将 32 位无符号整数分配给包含 32 位的位域
【发布时间】:2019-08-15 21:55:40
【问题描述】:

我正在尝试创建一个总共有 32 位的位域结构,但是当我尝试为其分配一个 32 位数字时,我收到此错误:

从 'unsigned int' 到位域的隐式截断将值从 4278190080 更改为 0

这是我的结构以及我尝试使用它的方式

struct Color32 {
    uint32_t a : 8;
    uint32_t r : 8;
    uint32_t g : 8;
    uint32_t b : 8;
};


Color32 BLACK = {0xFF000000}; // this line has the compilation error

我看到other questions 围绕位字段分配,但它们似乎都使用按位运算来设置各个字段。

还有this reference有下面的样例,好像和我用的一样,只有我的编译不了:

#include <iostream>
struct S {
 // three-bit unsigned field,
 // allowed values are 0...7
 unsigned int b : 3;
};
int main()
{
    S s = {6};
    ++s.b; // store the value 7 in the bit field
    std::cout << s.b << '\n';
    ++s.b; // the value 8 does not fit in this bit field
    std::cout << s.b << '\n'; // formally implementation-defined, typically 0
}

【问题讨论】:

  • 好的,你已经定义了一个位域大小为 n 的结构体。当然,对于最顶层的实现,一些构造函数是必要的。我不清楚的是,如果您需要 8 位固定大小,为什么要使用多个 32 位整数?如果您正在尝试制作处理器指令,为什么不制作结构化联合?
  • 也许类似的东西会有所帮助:stackoverflow.com/questions/3065948/c-unions-bit-fields-task
  • 使用操作设置单个字段有什么问题?这样您就可以控制初始化数据的不同部分的去向,而无需进行试验。

标签: c++ bit-fields


【解决方案1】:

你可以在这里使用aggregate initialization

Color32 BLACK = {0xFF, 0x00, 0x00, 0x00};

顺便说一句,我建议将您的 Color32 结构修改为以下内容,这与指定成员的位字段具有相同的效果

struct Color32 {
    uint8_t a;
    uint8_t r;
    uint8_t g;
    uint8_t b;
};

【讨论】:

  • 我只是想补充一点(因为 OP 似乎想要 uint32_t 行为),然后您可以创建一个接受 uint32_t 作为输入参数的构造函数,并使用 operator uint32_t()重载使其像常规整数一样工作。
  • 我也是从uint8_t 成员开始的,但开始过度思考,以至于我问了一个问题“为什么我不能将这一切都设置为一个整数?”但是,我认为您的答案可能是最好的方法,同时避免未定义的行为并且更便携。
【解决方案2】:

这样的事情会让你两全其美:

    struct Color32 {
    union {
        uint32_t color;
        struct {
            uint32_t b : 8;
            uint32_t g : 8;
            uint32_t r : 8;
            uint32_t a : 8;
        };
    };
};

// will construct using single value
Color32 test{ 0x01020304 };
Color32 black{0xff000000 };

// can assign to individual fields
test.a = 0x04;
test.r = 0x03;
test.g = 0x02;
test.b = 0x01;

// can assign to the whole value like this.
test.color = 0xff000000;
test.color = black.color;

这个问题是结构中 a、b、g、r 的顺序可能取决于您的特定编译器。对于 VS2017 编译到 windows 目标,显示的顺序将产生预期的结果。我相信可能有办法以某种方式强制命令,但我不熟悉如何做到这一点。

【讨论】:

  • 伟大的思想都一样。强制字节序的方法是选择一个并坚持下去。这意味着通过宏或函数检测操作系统字节序,并操作数据,无论是位/小字节序还是中间字节序。这就是联合和指针很棒的原因。您可以毫无问题地交换数据(并将它们包含在类型空间中)。你可以在这里做类似的事情( a 变成 b, r 变成 g )但是跟踪并不有趣。所以经常使用代理结构来不弄乱结构。
  • @Danilo - 便携狂热者讨厌这种东西。对我来说,我的目标是固定的,我一直在做这种事情。我想有一天它会适得其反。
  • 好吧,如果你只为你的机器编码,那应该不是问题。但是,如果您希望使用与服务器通信的协议(IP、UDP....),您将需要使用不同的服务器端和客户端代码。如果您没有使用互联网通信,那么您就是黄金 :D 但是如果需要的话 :stackoverflow.com/a/1001373/3142882
  • 这很好。我可能会在某个地方使用它。
  • 另一个问题是你很容易得到未定义的行为。编译器不仅可以选择结构中字段的顺序,还可以确定以下语句序列是否有效(it's officially undefined behavior):black.color = 0xff000000; test.a = black.a;。
【解决方案3】:

Bitfield 与否,您的类型有 四个 成员,而不是一个。

你似乎想把它当作一个工会来对待。

像使用任何其他类型一样单独初始化每个成员,或者切换到联合(然后像许多人一样依赖类型双关语,但通常需要注意事项)。


你给出的反例不一样,因为它是一个UDT,在初始化器中只有一个成员和一个值;由于给定的成员数量匹配,所以那里一切都很好。

【讨论】:

    【解决方案4】:

    在深入探讨该主题后,我发现如果没有按位运算符和有效的构造函数,多个位字段将毫无用处,这在很大程度上取决于操作系统。

    答案在 windows 7 上的 cygwin ( -Wno-unused-variable -O0 -ggdb flags ) 上测试过

    版本 1:union

    这是没有任何位域的基本实现,最常见的 4 字节颜色空间实现。

    #include <iostream>  
    
    union c_space32{
        uint32_t space;
        uint8_t channels[4];
    };
    
    int main(){
    
        { // just a anonymous scope to keep things clear 
            union c_space32 temp = {0xff00fe32};
            std::cout << "sizeof : " << sizeof( union c_space32 ) << "\n\n";
            std::cout << (int)temp.channels[1] << "\t" << std::hex << temp.space <<  "\n";
            ++temp.channels[1];
            std::cout << (int)temp.channels[1] << "\t" << std::hex << temp.space <<  "\n";
            ++temp.channels[1];
            std::cout << (int)temp.channels[1] << "\t" << std::hex << temp.space <<  "\n";
        }
    
    return 0;}  
    

    联合表现为正常颜色空间,联合的每个uint8_t 部分表现为唯一字节,因此c_space32.channels 中值的整体变化不会影响c_space32.space 范围之外的值字节。这是我得到的输出。

    sizeof : 4
    fe  ff00fe32
    ff  ff00ff32
    0   ff000032  
    

    版本 2:bit-fields

    位域的问题(在某些情况下缺乏文档)是它们的大小很容易改变,并且字节序取决于操作系统,因此位域结构背后的本机逻辑可以逃避我们的人类逻辑。让我给你一些例子,供未来希望努力进入这个话题的人/女孩。

    #include <iostream> 
    #include <bitset> 
    
    struct temp1{
        uint8_t a:1;
        temp1(uint8_t val){ // just so i can assign it
            this->a = (val & 0x1 ); // this is needed to avoid truncated warning
        }
    };
    
    int main(){
        struct temp1 t1 = 3;
        uint8_t *ptr = (uint8_t *)&t1;
        std::cout << sizeof(struct temp1) << std::endl; // size of 1 byte
        std::cout << std::bitset<8>( *ptr ) << std::endl; // 0000-0001 position of our bitfield
    return 0;}
    

    所以在这种情况下sizeof(struct temp1) 返回大小为 1 byte。我们的位字段的位置为最右上。这里是文档开始走向 MIA 的地方。

    #include <iostream> 
    #include <bitset> 
    
    struct temp2{
        uint8_t a:1;
        uint8_t b:1;
        uint8_t c:1;
        temp2(int VAL){ // just so i can assign it
            this->a = (VAL & 0x1 );
            this->b = 0;
            this->c = (VAL >> 2 ) & 0x1;
        }
    };
    
    int main(){
        struct temp2 t1 = 0xf;
        uint8_t *ptr = (uint8_t *)&t1;
        std::cout << sizeof(struct temp2) << std::endl; // size of 1
        std::cout << std::bitset<8>( *ptr ) << std::endl; // 0000-0101
    return 0;}
    

    在这种情况下constructor 是必须拥有,因为计算机不知道您想如何构造数据。当然,在我们的逻辑中,如果我们将位对齐,那么分配它们与共享内存相同是合乎逻辑的。但问题是计算机不会为我们做bitwise operators。当然这些位是按顺序排列的,但计算机只是抓取一些位并将其定义为一个唯一变量,您选择在该变量中放置什么由您决定。

    如果我们超出 unit memory size (byte) 的范围,操作系统就会开始干扰我们的工作。

    #include <iostream> 
    #include <bitset> 
    
    struct temp3{
        bool b0:1;
        bool b1:1;
        bool b2:1;
        bool b3:1;
        bool b4:1;
        bool b5:1;
        bool b6:1;
        bool b7:1;
    
        temp3( int a ){
            this->b0 = ( a & 0x1 );
            this->b1 = ( a & 0x2 );
            this->b2 = ( a & 0x4 );
            this->b3 = ( a & 0x8 );
            this->b4 = ( a & 0x10 );
            this->b5 = ( a & 0x20 );
            this->b6 = ( a & 0x40 );
            this->b7 = ( a & 0x80 );
        }
    
    };
    
    int main(){
        struct temp3 t1 = 0xc3;
        uint8_t *ptr = (uint8_t *)&t1;
        std::cout << sizeof(struct temp3) << std::endl; // still size of 1
        std::cout << std::bitset<8>( *ptr ) << std::endl; // 1100-0011
    return 0;}  
    

    当我们超过字节大小时:

    #include <iostream> 
    #include <bitset> 
    struct temp4{
        bool b0:1;
        bool b1:1;
        bool b2:1;
        bool b3:1;
        bool b4:1;
        bool b5:1;
        bool b6:1;
        bool b7:1;
        bool b8:1;
    
        temp4( int a ){
            this->b0 = ( a & 0x1 );
            this->b1 = ( a & 0x2 );
            this->b2 = ( a & 0x4 );
            this->b3 = ( a & 0x8 );
            this->b4 = ( a & 0x10 );
            this->b5 = ( a & 0x20 );
            this->b6 = ( a & 0x40 );
            this->b7 = ( a & 0x80 );
            this->b8 = ( a & 0x100 );
        }
    
    };
    
    int main(){
        struct temp4 t1 = 0x1c3;
        uint16_t *ptr = (uint16_t *)&t1;
        std::cout << sizeof(struct temp4) << std::endl; // size of 2
        std::cout << std::bitset<16>( *ptr ) << std::endl; // 0000-0000 1100-0011
        std::cout << t1.b8 << std::endl; // still returns 1
        std::cout << "\n\n";
    
        union t_as{
            uint16_t space;
            temp4 data;
            uint8_t bytes[2];
        };
    
        union t_as t2 = {0x1c3};
        //11000011-00000001
        std::cout << std::bitset<8>( t2.bytes[0] ) << "-"  << std::bitset<8>( t2.bytes[1] ) << std::endl;
    return 0;}  
    

    这里发生了什么?由于我们添加了另一个 bool bit-field 我们的结构增长了 1 个字节(因为 bool 是 1 个字节),我们的 16 位指针不显示最后一个 @ 987654339@ - 但工会确实如此。问题是操作系统接管了,在这种情况下,由于先天的操作系统字节序,最后一点停留在我们原始内存后面。正如您在 union 中看到的,字节仍然被读取,但顺序不同。

    因此,当超过字节大小时,将应用正常的操作系统规则。

    结论和答案

    struct half_opacity{
        uint8_t alpha:4;
        uint8_t red;
        uint8_t green;
        uint8_t blue;
        half_opacity(int a){
            this->alpha = ( a >> 24 )&0xf;
            this->red   = ( a >> 16 )&0xff;
            this->green = ( a >> 8  )&0xff;
            this->blue  = ( a & 0xff );
        }
        operator uint32_t(){
            return      ( this->alpha << 24 )
                    |   ( this->red   << 16 )
                    |   ( this->green << 8 )
                    |   this->blue;
        }
    };
    
    {
        struct half_opacity c_space = 0xff00AABB;
        std::cout << "size of : " << sizeof(struct half_opacity) << std::endl; //size of : 4
        std::cout << std::hex << (uint32_t)c_space << std::endl; // 0x0f00AABB  
    }
    

    因此,除非您打算将原始通道设置为某个位大小,否则我强烈建议您使用union 方法,因为使用bit-fields 将 32 位整数拆分为单个字节并没有任何额外的好处。 bit fields 的主要问题是您需要拆分它们并像任何其他整数字段一样构建然后备份 - bit shifts 通常会规避整个操作系统字节序。

    您收到的截断警告是由于您的 struct 中有多个成员,并且 struct 自然分配了第一个成员,并且由于您添加的数量超过了 bit field 的处理能力,编译器警告您一些数据将会丢失。

    【讨论】:

      猜你喜欢
      • 2013-09-18
      • 2011-01-31
      • 1970-01-01
      • 2013-06-21
      • 1970-01-01
      • 1970-01-01
      • 2011-03-09
      • 2019-05-30
      • 2012-07-27
      相关资源
      最近更新 更多