【问题标题】:Using bitfield-structs in binary de-serialization: is layout guaranteed on GCC?在二进制反序列化中使用位域结构:GCC 是否保证布局?
【发布时间】:2023-01-18 00:59:36
【问题描述】:

因此,我正在编写一个将用于反序列化二进制数据流的结构。为了说明这一点,这里有一个精简版:

typedef struct
{
    bool flag1 : 1;
    bool flag2 : 1;
    bool flag3 : 1;
    bool flag4 : 1;
    uint32_t reserved : 28;
} frame_flags_t;

typedef struct
{
    /* Every frame starts with a magic value. */
    uint32_t magic;

    frame_flags_t flags;

    uint8_t reserved_1;
    
    /* A bunch of other things */

    uint32_t crc;
} frame_t;

我的问题是,如果执行以下操作:

frame_t f;

memcpy(&f, raw_data_p, sizeof(frame_t));

我能保证 f.flags.flag1 真的是第一位(在 magic 成员之后,假设是一个整齐打包的结构(它是))?而那个.flags2 将是紧随其后的那个,等等?

据我了解,C 和 C++ 标准并不能保证这一点。海湾合作委员会吗?

【问题讨论】:

  • 显然这是未指定的行为。即使 GCC 不填充这个结构,我也不会使用它...... en.cppreference.com/w/c/language/bit_field
  • 您在那里有多个问题:1)位域具有实现定义的行为。它们不需要以您可能期望的方式放置。而且您似乎假设该结构为 32 位大。当使用不同的类型时,你不应该依赖它。 2) 你的结构 frame_t 可能包含一些填充字节。 3)你的机器的字节序不需要匹配你流中数据的字节序。
  • 我相信,如果您将自己限制在小端架构(或大端架构,如果您能找到的话),您将获得一致的结果。我也相信使用原始结构进行反序列化是一个糟糕的主意,但这是一种非常流行的技术,所以不要让我阻止你。 :-)
  • 使用位域,甚至是普通结构来进行序列化并不是一个好主意。但是既然你问了,this GCC manual page 说基本上是“由 ABI 决定。”.
  • @user694733 你有什么建议而不是反序列化?一次 memcpying 每个成员? (并使用.flag1 = rawmemory & bitmask;作为位域)?

标签: c gcc bit-fields


【解决方案1】:

我能保证 f.flags.flag1 真的是第一位吗(在 magic 成员之后,假设是一个整齐打包的结构(它是))?

C语言不保证,不。

而那个.flags2 将是紧随其后的那个,等等?

C语言要求分配给同一可寻址存储单元的连续位域之间没有间隙。这很可能意味着标志最终会占据同一字节中的相邻位,但事实并非如此就是那个意思

据我了解,C 和 C++ 标准并不能保证这一点。海湾合作委员会吗?

否。结构布局规则是应用程序二进制接口(ABI),这是操作系统+硬件组合的属性。例如,在 x86_64 上运行的 Linux 有一个 ABI,在 32 位 x86 上运行的 Linux 有一个不同的 ABI,在这些平台上运行的 Windows 也有不同的 ABI。 GCC 支持各种各样的 ABI,它根据目标 ABI 的规则来布局结构。它不能对结构布局的细节做出任何笼统的保证。

例如,相关的适用于 Linux / x86_64 的 ABIhttps://www.intel.com/content/dam/develop/external/us/en/documents/mpx-linux64-abi.pdf。关于位域布局,它说:

位域遵循与其他结构相同的大小和对齐规则 和工会会员。

还:

  • 位域是从右到左分配的
  • 位字段必须包含在适合其声明类型的存储单元中
  • 位域可以与其他结构/联合成员共享一个存储单元

这实际上并不完全一致,但它对您的frame_flags_t 的解释方式是:

  • 该结构的大小为 4,由一个大小相同的“可寻址存储单元”组成,所有位域都被打包到该单元中
  • flag1 使用最低有效位
  • flag2 使用倒数第二位
  • flag3 使用倒数第三位
  • flag4 使用倒数第四位

此外,整个frame_t 结构在 Linux / x86_64 上有 4 字节对齐要求,它将以对齐所有成员所需的最小填充进行布局。因此,在这样的机器上,magic 成员和flags 成员之间将没有填充。 x86_64 是 little-endian,因此确实会将标志位放在 magic 之后的第一个字节中在 Linux / x86_64 上.

【讨论】:

  • 谢谢。与此同时,我被指向gcc.gnu.org/onlinedocs/gcc/…,它确认了“一个单元内位域的分配顺序(C90 6.5.2.1、C99 和 C11 6.7.2.1)。-> 由 ABI 确定。”。
  • 此外,这是针对 ARM v7 的,但我从 X86 示例中得到了这个想法。
  • 我很高兴它有所帮助。这里至少有两条消息,@RuiOliveira。一是您可以咨询适当的 ABI 以获得有关给定构​​建的此类详细信息。第二个是如果你想写便携的软件,那么您将不会依赖结构布局细节,尤其是在涉及位域时。
  • 是的。这适用于在单个设备上运行的特定于平台的应用程序。但该消息对于任何便携式设备都是值得注意的。
【解决方案2】:

此外,这是针对 ARM v7 的,

在此目标上,您可以安全地使用位域,它们的行为由 ABI 明确指定。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-11-28
    • 2014-09-20
    • 2014-01-04
    • 1970-01-01
    • 2011-01-21
    • 2016-09-25
    • 2015-11-23
    • 1970-01-01
    相关资源
    最近更新 更多