与位域的结合为位域成员带来了意想不到的价值

我具有以下构造,意在获取包含四个12位值的48位值并提取它们。

struct foo {
    union {
        unsigned __int64 data;
        struct {
            unsigned int a      : 12;
            unsigned int b      : 12;
            unsigned int c      : 12;
            unsigned int d      : 12;
            unsigned int unused : 16;
        };
    };
} foo;

有问题的数据然后使用

进行分配
foo.data = (unsigned __int64)Value;

此处最初是用于存储数据的双精度值。

我对位字段的假设是

  • 数据保存变量应足够大以容纳整个 数据和未签名。因此, unsigned __int64 可容纳48位。
  • 每个位域成员的类型应足以容纳 分配给它且未签名的位数。
  • 如果所有位域成员都保持相同类型,则是一个好主意 可能。 (以避免对齐问题?)
  • 实际上不需要未使用成员。

这些对吗?

测试

Value = 206225551364

我们得到一个,其中应包含位

0000 0011 0000 0000 0100 0000 0000 0011 0000 0000 0100‬

这应该导致

a: 0000 0000 0100‬ = 4
b: 0000 0000 0011 = 3
c: 0000 0000 0100 = 4
d: 0000 0000 0011 = 3

但是运行此返回的实际值是

a: 4
b: 3
c: 48
d: 0

尽管这些值应在 unsigned int的范围内,但有时会更改所使用的类型。因此,感觉就像与将数据添加到位字段后如何解释数据有关。

通过添加 #pragma pack(1),据我所知,它与对齐方式有关,但是使用频率不是很高,我突然得到了预期的值。

struct foo {
    union {
        unsigned __int64 data;
#pragma pack(1)
        struct {
            unsigned int a      : 12;
            unsigned int b      : 12;
            unsigned int c      : 12;
            unsigned int d      : 12;
            unsigned int unused : 16;
        };
    };
} foo;

a: 4
b: 3
c: 4
d: 3

但是我不愿意接受这一点。我想了解它,从而确保它确实有效,并且看起来不仅仅在值不超过4位的情况下起作用。

所以

  • 为什么我看到这个问题开始?
  • #pragma pack 语句可以解决什么问题?
  • 有人能推断出什么时候会成问题吗?是因为 值是12位而不是8位或16位吗?
zhenhao1986 回答:与位域的结合为位域成员带来了意想不到的价值

长话短说-无论您在做什么,通过union投射数据都是不确定的行为。因此,它可以正常工作,而并非偶然。只有您被允许对union进行的操作才能读取您上次写给该成员的成员。您执行其他任何操作,程序无效。

编辑:

即使允许这样做,如果没有#pragma pack,您也将依赖于结构中的数据对齐。大概是32位或64位。因此,在这种情况下,您的结构实际上在内存中看起来像这样:

struct {
    unsigned int a      : 12;
    unsigned int a_align: 20;
    unsigned int b      : 12;
    unsigned int b_align: 20;
    unsigned int c      : 12;
    unsigned int c_align: 20;
    unsigned int d      : 12;
    unsigned int d_align: 20;
    unsigned int unused : 16;
    unsigned int unused_align: 16;

};

如果要从结构中提取一些数据,则可能应使用如下的掩码和位移位:

unsigned mask12 = 0xFFF;//1 on first 12 least significant bits
unsigned a = data & mask12;
unsigned b = (data >> 12) & mask12;
unsigned c = (data >> 24) & mask12;
unsigned d = (data >> 36) & mask12;
,
  

为什么我会看到这个问题?

正确地,访问联盟的不活动成员具有未定义的行为。但是让我们假设您的系统允许它。

unsigned可能是32位。 a和b放入第一个unsigned中,总共占用24位。 unsigned仅剩8位。 12位c不适合该8位插槽。因此,它改为启动一个新的unsigned并保留8位填充。

这是一种可能的结果。位域布局由实现定义。在另一个系统上,您可能会看到预期的结果。或者输出的结果与您的预期不同,与您在此处观察到的有所不同。

  

#pragma pack语句可以解决此问题吗?

它可能会更改布局规则,以允许跨多个基础对象“跨越”位域。这可能会使其访问速度稍微慢一些。

  

有人能推断出什么时候会成为问题吗?

如果您不尝试跨越基础对象,那么布局是否支持该对象就没有区别。在这种情况下,您可以简单地使用64位基础对象。

这不是位域布局可能与您期望的唯一不同的唯一方式。例如,位域可能是最重要的第一位或最后一位。 unsigned本身的位数是实现定义的。

通常,不应该依赖位集的布局。

  

那我想要达到的最佳效果怎么办?

要避免UB,您可以创建另一个对象,然后从一个字节复制另一个字节,而不是通过联合修剪。但是首先,必须确保对象具有相同的大小。可以使用std::memcpystd::bit_cast完成复制。

为避免跨界问题,请使用完全填充每个基础对象的位域集。在这种情况下,使用64位基础对象。

要获得可靠的布局,请不要首先使用位域bartop展示了如何使用移位和遮罩来做到这一点。 (尽管布局仍然依赖于字节序)

本文链接:https://www.f2er.com/3057960.html

大家都在问