【问题标题】:Compiler adds padding to already aligned union bit field member编译器向已对齐的联合位字段成员添加填充
【发布时间】:2015-05-28 08:28:03
【问题描述】:

我正在尝试构建自己的库,用于在不同字节序的整数和有符号表示之间进行转换。其中一部分涉及直接访问 char 缓冲区作为相同大小的整数(主要用于快速比较)。我非常担心这个项目的可移植性。&nbsp 我希望这个库可以在所有机器和新旧 c 编译器上使用,而不仅仅是 gcc 或 linux 上的 clang。

最初我对整数成员使用 c99 固定宽度整数类型。&nbsp 这很好用。&nbsp 当我从固定宽度整数切换到相同大小的位字段时,问题就开始了。我这样做是为了通过不依赖 c99 功能来提高可移植性。&nbsp

问题是,当我使用位字段时,gcc 抱怨已插入填充(-Werror 和 -Wpadded 已传递给 gcc):
错误:将结构大小填充到对齐边界

以下是其中一个联合的示例:

旧版本,运行良好:

typedef union
{
    unsigned char bytes[4];
    uint32_t uvalue;
} upicl_uint32_be;

新版本,添加了不需要的填充:

typedef union
{
    unsigned char bytes[4];
    unsigned long uvalue : 32;
} upicl_uint32_be;


我不知道为什么当 32 位位字段已对齐时,为什么要向联合中添加填充。

有没有更好的方法来做到这一点?

【问题讨论】:

  • 在这种情况下,更好的方法是memcpy。老实说,如果使用 C 编译器对任何东西的可移植性是一个问题,那么如果担心你将不得不放弃位域作为一个失败的原因。如果你不走运,你的编译器可能不支持长位域。如果您真的不走运,它甚至可能没有 8 位字符。哦,你确定你的 C 编译器没有 >32 位长吗?
  • 我的代码检查以确保 CHAR_BIT == 8 并且除非这是真的,否则不会编译。
  • 那么你已经放弃支持所有编译器了吗? ;)
  • 抱歉造成误会。我指的是绝大多数编译器(在具有 8 位字符的普通系统上)。
  • Zeb Thompson:抱歉,我不是故意轻视您在确保便携性方面所做的努力。我的意思是在完全符合标准的 C 中做任何实际工作本质上是在浪费时间(想想 C89 中外部标识符中的 6 个重要字符)。正如您似乎正在做的那样,诀窍是选择一个适合您的应用程序的合理子集,并希望您做出了明智的选择。

标签: c padding unions bit-fields


【解决方案1】:

假设您需要一个 4 字节的 unsigned long native,您可以使用对齐语法来设置它。

typedef union
{
    unsigned long unused : 0;
    unsigned char bytes[4];
    unsigned long uvalue : 32;

} upicl_uint32_be;

可能会成功。

另一种可能性是颠倒顺序,以便编译器首先看到 unsigned long。

typedef union
{
    unsigned long uvalue : 32;
    unsigned char bytes[4];

} upicl_uint32_be;

尝试双精度的最后一件事通常是 8 个字节

typedef union
{
    double unused;
    unsigned char bytes[4];
    unsigned long uvalue : 32;

} upicl_uint32_be;

【讨论】:

    【解决方案2】:

    您似乎是在 64 位系统上编译代码,否则 unsigned long 将与 uint32_t 相同。您可以测试以下内容:

    typedef union {
       uint32_t uvalue : 1;
    } upicl_uint32_be;
    

    ...也给出警告。问题是您只有 1 位数据,但 uint32_t 强制它占用 32 位。

    如果您不想收到警告,请使用 unsigned int 类型。在几乎所有系统上,除了一些微控制器unsigned int 保证为 32 位。但在我看来,使用uint32_t 是最佳选择,因为它始终保证为 32 位。

    您可以编写一些#define hack 来查看哪种基本类型是 32 位,但我不推荐这样的 hack。考虑到 C99 已经超过 15 年了,所以几乎所有编译器都支持它。

    【讨论】:

    • 可悲的是,虽然<stdint.h> 在一般情况下相当普遍,但完整的 C99 支持更多的是例外而不是规则。众所周知,MSVC 仍然不会打扰,并且在我度过我的日子的嵌入式世界中,除非编译器是基于 GCC 的,否则它几乎是一种注销。也许有一天我可以开始利用所有这些漂亮的新功能..
    • 我想这意味着我应该坚持使用 c99 固定宽度整数类型。感谢您的帮助。
    • 我认为大多数没有实现 C99 的编译器仍然有 <stdint.h> 所以你应该安全地使用它。如果您遇到没有<stdint.h> 的编译器,您可以使用#define hacks 来模拟它。如果您已经支持<stdint.h>,我不建议使用这些#define hack。
    【解决方案3】:

    long 可能是 8 个字节长,因此位域将占用 8 个字节,因为它与 uint32_t 的大小不同。

    联合类型的名称表明您希望位域基于uint32_t 而不是unsigned long(或者您假设两者相同,这显然是不正确的)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-27
      • 1970-01-01
      • 2011-09-29
      • 2016-09-18
      • 1970-01-01
      相关资源
      最近更新 更多