c++ - 与位域的 union 为位域成员提供了意想不到的值(value)

标签 c++ unions bit-fields

我有以下构造,旨在获取包含四个 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;

Value 这里最初是一个用于存储数据的 double 值。

我在创建位字段时的假设是

  • 数据保存变量应该足够大以保存整个 数据且未签名。因此,无符号 __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

虽然这些值应该适合无符号整数,但有时使用的类型切换会改变值。所以感觉它与添加到位字段时如何解释数据有关。

通过添加 #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 位?

最佳答案

why am I seeing the issue to begin with?

首先,访问 union 体的非事件成员具有未定义的行为。但让我们假设您的系统允许这样做。

unsigned 可能是 32 位。 a 和 b 适合第一个 unsigned,总共 24 位。这个unsigned只剩下8位了。 12 位 c 不适合这个 8 位插槽。因此,它会启动一个新的 unsigned,留下 8 位填充。

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

What does the #pragma pack statement do that fixes the issue?

它可能会更改布局规则,以允许位域“跨越”多个底层对象。这可能会使访问速度有点变慢。

Can one deduce when this will become a problem and not?

如果您不尝试跨过底层对象,那么布局是否支持这一点不会有任何差异。在这种情况下,您可以简单地使用 64 位底层对象。

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

一般来说,位集的布局不值得依赖。

How would, what I want to achieve best be done then?

为了避免 UB,您可以创建另一个对象,然后将字节复制到另一个对象上,而不是通过 union 进行双关。但首先,您必须确保对象具有相同的大小。可以使用 std::memcpystd::bit_cast 完成复制。

为了避免跨接问题,请使用完全填充每个底层对象的位域集。在本例中,使用 64 位底层对象。

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

关于c++ - 与位域的 union 为位域成员提供了意想不到的值(value),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58975580/

相关文章:

c++ - 创建具有固定顶部坐标的可调整大小的窗口

c++ - 为什么 push_back 和 emplace_back 组合在一起时会有不同的行为

c++ - 如何调用模板方法

c - 打包结构的 union

c - 结构内部的匿名 union

c# - 使用标志枚举的优点和缺点是什么?

c++ - 启动、停止和发送参数以将 Python 脚本与 C++ 应用程序分开的最佳方法?

c++ - 为什么为位字段分配值不返回相同的值?

c - 我怎样才能让 GCC 将这个位移指令优化为一个 Action ?

这个 union 能在小端机器上给出意想不到的值吗