结构体填充信息

4
struct abc
{
  char arr[7];
  char arr1[2];
  int i:24;
};

在使用 sizeof 运算符的上述结构中,我得到了它的大小为12个字节。但根据我的计算(也许是错误的),它应该是16字节。为什么它给出的是12个字节?
另一个问题:
根据 C99 第6.7.2.1段第14款,
“结构或联合对象的每个非位域成员都以适合其类型的实现定义方式对齐。”
对于特定的实现,在哪里可以获得描述特定编译器(例如gcc)如何在结构中引入填充的文档?是否有所有编译器通用的规则适用于特定体系结构?

3
Implementation defined 的意思是每个实现都可以自由地选择他们想要的方式来实现某一特性(没有通用规则),但他们需要记录下这种行为。因此,这份记录需要由相应的编译器提供,并且如果你进行搜索,你应该能够找到它。 - Alok Save
为什么是16?char数组不需要对齐,而i是一个三字节的位域,因此它是7+2+3=12。 - Sergey Kalinichenko
通常情况下,实现遵循平台ABI。任何违反平台的编译器都将自我孤立,无法进行互操作。 - David Heffernan
1
@EF arr 是七字节,arr1 是两字节,i 是三字节。 - Sergey Kalinichenko
2个回答

2
结构布局是由实现定义的。例如,GCC使用的默认布局与MSVC使用的布局不同。我猜你习惯了包含位域的结构体的MSVC布局方式。
当然,GCC有一个属性叫做“ms_struct”,可以让你改变行为。这在文档中有更详细的描述。
所以,这个结构体的大小是16:
struct abc
{
  char arr[7];
  char arr1[2];
  int i:24;
} __attribute__((ms_struct));

如果您使用默认的gcc_struct选项,则大小为12。
对于特定的实现,我该如何获取描述特定编译器(例如GCC)如何在结构中引入填充的文档?
您需要查阅每个编译器的文档。在GCC的情况下,文档指出4.9 结构体、联合体、枚举和位域
  • 通过使用不同类型的成员,访问联合对象中的一个成员 (C90 6.3.2.3). 相关对象的表示中的字节被视为所用于访问的类型的对象。请参阅类型转换。这可能是陷阱表示。

  • “plain” int 二进制位域如何处理,是作为有符号int还是无符号int 二进制位域来处理 (C90 6.5.2, C90 6.5.2.1, C99 6.7.2, C99 6.7.2.1)。默认情况下处理为有符号int,但可以通过-funsigned-bitfields选项进行更改。

  • 允许的非_Bool、有符号int和无符号int类型的位域(C99 6.7.2.1)。在严格符合模式下不允许使用其他类型。

  • 位域是否可以跨存储单元边界( C90 6.5.2.1, C99 6.7.2.1)。由ABI确定。

  • 单位内分配位域的顺序 (C90 6.5.2.1, C99 6.7.2.1)。由ABI确定。

  • 结构体非位域成员的对齐方式 (C90 6.5.2.1、C99 6.7.2.1)。由ABI确定。

  • 与每个枚举类型兼容的整数类型(C90 6.5.2.2、C99 6.7.2.2)。通常,如果枚举中没有负值,则为unsigned int类型,否则为int类型。如果指定了-fshort-enums,则如果存在负值,则为signed char、short和int中可以表示所有值的第一个,否则为unsigned char、unsigned short和unsigned int中可以表示所有值的第一个。

    在某些目标上,默认情况下使用-fshort-enums;这由ABI确定。

所以,大体上,您需要确定平台的ABI。对于任何编译器来说,这真的是一个理智的想法。如果它不按照ABI布局结构,则使得互操作极其棘手。

有点奇怪的是,GCC在Windows下对ABI的看法与MSVC实现不同。我不知道为什么会这样。


1

这很简单。

struct abc
{
  char arr[7];  // occupies 7 bytes
  char arr1[2]; // occupies 2 bytes
  int i:24;     // occupies 3 bytes
};

现在,在第三个变量 i 的声明中,只需要3个字节。您已经如下所示:

0 1 2 3 // All 4 bytes used for `char arr[7]`
0 1 2 3 // 3 more used for `char arr[7]`, 1 used for `char arr1[2]`
0 1 2 3 // 1 used for `char arr1[2]`, and the remaining 3 bytes will be used for `int i:24`

但是如果你使用int i(不使用位域),它将消耗16个字节,因为

0 1 2 3 // All 4 bytes used for `char arr[7]`
0 1 2 3 // 3 more used for `char arr[7]`, 1 used for `char arr1[2]`
0 1 2 3 // 1 used for `char arr1[2]`, there are still 3 bytes but we need 4 bytes for an `int`
0 1 2 3 // So the compiler will allocate a new 4 byte chunk for `int i`

我认为现在很清楚了。


1
不同的编译器在arr1后面有3个字节的填充,在i后面有1个字节的填充。那么,问题是为什么会这样呢?如果结构体的大小为12,那么它的布局应该很明显。我想不出其他的方法来做到这一点。 - David Heffernan
这取决于编译器如何管理其内存。在GCC中,您提到的相同结构将被大小为12字节,而在Visual C编译器中,它将被大小为16字节。因为它取决于编译器如何遵循标准并实现它。 - surender8388
很好。这就是问题的关键。我相信@fuzzy可以看出12字节布局是如何排列的。 - David Heffernan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接