【问题标题】:Size of struct with bitfields different between Linux (gcc) and Windows (mingw32 gcc)Linux (gcc) 和 Windows (mingw32 gcc) 之间位域不同的结构大小
【发布时间】:2015-07-10 20:32:29
【问题描述】:

类似的问题,但特定于打包结构: Why would the size of a packed structure be different on Linux and Windows when using gcc?


我正在为需要通过网络连接处理结构良好的数据的 Linux 和 Windows 构建一个共享库。我在 Linux 上使用 gcc 4.8.2,并使用 i686-pc-mingw32-gcc 4.8.1 交叉编译 Windows 目标。

我制作了这个小程序来演示这个问题(注意 GCC 属性已被注释掉,留作参考):

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

typedef uint16_t word_t;

typedef enum //__attribute__((__packed__))
{
  PRIO_0 = 0,
  PRIO_1,
  PRIO_2,
  PRIO_3,
  PRIO_4,
  PRIO_5,
  PRIO_6,
  PRIO_7,
}
prio_t;

typedef enum //__attribute__((__packed__))
{
  FLAG_A = 0,
  FLAG_B,
}
flag_t;

typedef struct //__attribute__((__packed__))
{
  word_t id     : 8;
  prio_t prio   : 3;
  flag_t flag_1 : 1;
  flag_t flag_2 : 1;
  flag_t flag_3 : 1;
  flag_t flag_4 : 1;
  word_t spare  : 1;
}
recd_t;


int main(int argc, char *argv[])
{
#define NAME_WIDTH 32

  printf("%-*s = %lu\n", NAME_WIDTH, "sizeof(prio_t)", (unsigned long)sizeof(prio_t));
  printf("%-*s = %lu\n", NAME_WIDTH, "sizeof(flag_t)", (unsigned long)sizeof(flag_t));
  printf("%-*s = %lu\n", NAME_WIDTH, "sizeof(recd_t)", (unsigned long)sizeof(recd_t));

  return 0;
}

我正在为 Linux 编译使用: gcc -g -Wall test.c -o ./test

和 Windows: i686-pc-mingw32-gcc -g -Wall test.c -o ./test.exe

我认为非常简单。在 Linux 上运行时,输出是我所期望的:

sizeof(prio_t)                   = 4
sizeof(flag_t)                   = 4
sizeof(recd_t)                   = 4

但在 Windows 上:

sizeof(prio_t)                   = 4
sizeof(flag_t)                   = 4
sizeof(recd_t)                   = 12

那么,Windows 尺寸有什么问题呢?为什么在这种情况下它们与 Linux 不同?


我最终需要打包这些枚举和结构,但这个问题会在打包完成之前出现。但启用后,结果相似:

Linux:

sizeof(prio_t)                   = 1
sizeof(flag_t)                   = 1
sizeof(recd_t)                   = 2

窗户:

sizeof(prio_t)                   = 1
sizeof(flag_t)                   = 1
sizeof(recd_t)                   = 6

【问题讨论】:

  • 如果你真的想要“通过网络连接结构良好的数据”,我的建议是尝试使用打包结构来实现。我知道这就是它们的用途,但是让它工作起来可能非常困难,而且你仍然无法解决字节顺序问题。请参阅this question 的进一步讨论。
  • 为什么是uint16_t word_t; 而不是uint8_t word_t;
  • @SteveSummit 感谢您的链接。不确定您是否假设与字节序注释进行 IP 通信,但它很可能会使用 ARINC 429。不希望网络详细信息影响问题
  • @alk 外部接口定义。不是我的选择:)
  • 如果您有任何可移植的想法:(1) 不要使用位域,(2) 参见 1,(3) 序列化数据而不是使用打包结构。

标签: c windows gcc struct bit-fields


【解决方案1】:

C 规范有一个信息性附件(附件 J),它总结了未指定的行为、未定义的行为和实现定义的行为。以下是关于位域的内容。

J.3 实现定义的行为

一个符合要求的实现需要记录它的选择 在本子条款中列出的每个领域中的行为。以下 是实现定义的:

J.3.9 结构、联合、枚举和位域

  • “普通”int 位域被视为有符号整数位域还是无符号整数位域(6.7.2、6.7.2.1)。

  • _Bool、signed int 和 unsigned int (6.7.2.1) 以外的允许位域类型。

  • 是否允许位域使用原子类型 (6.7.2.1)。
  • 位域是否可以跨越存储单元边界 (6.7.2.1)。
  • 一个单元中位域的分配顺序 (6.7.2.1)。
  • 结构的非位域成员的对齐 (6.7.2.1)。这应该没有问题,除非二进制数据由一个 实现被另一个人读取。
  • 与每个枚举类型 (6.7.2.2) 兼容的整数类型。

您可以得出自己的结论,但我不会在旨在可移植的代码中使用位域。


似乎在 Windows 上,每次类型更改时编译器都会启动一个新的“单元”。因此,在未打包的情况下,您有一个 word_t(2 个字节),然后是一个 prio_t(4 个字节)、一个 flag_t(4 个字节)和另一个 word_t(2 个字节),总共 12 个字节。打包的时候是2,1,1,2,一共6个。如果你把所有的字段都声明为uint16_t,你可能会在windows上得到正确的大小,但是你仍然有的问题在一个单元内分配位域” 是实现定义的。

【讨论】:

  • 谢谢!看起来我仅在这个结构中使用了一些实现定义的行为。我没想到我的两个版本的 gcc 会有如此不同的行为(如果这是原因?)
  • 是的,我刚刚发现了同样的东西!用word_t 为每个成员替换我的枚举类型给我留下了我期望的结构大小。这意味着我只需要在读取和写入成员时进行适当的类型转换。无法将您的评论标记为答案,所以我会尽我所能:) 谢谢
  • @ardnew 不客气,我将评论移至答案以整理内容:)
  • @ardnew 看到不同的三元组有不同的实现定义的行为并不奇怪——每个三元组都算作一个单独的“实现”,没有“gcc 实现”,只有“x86-linux-gnu” 、“i686-pc-mingw32”、“x86_64-w64-mingw32”、“x86_64-linux-musl”、“aarch64-linux-gnu”等
  • @ardnew 请注意,在 C++ 中,您可以指定枚举的基础类型,但在 C 中,它始终是 unsigned intsigned intunsigned longsigned long、@ 中的第一个987654326@, signed long long 适合所有值,因此您可以尝试 unsigned int id : 8; 并保留枚举。
猜你喜欢
  • 2017-09-16
  • 2011-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-26
  • 1970-01-01
相关资源
最近更新 更多