1

考虑这段代码:

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

#ifdef __GNUC__
#define PACK( __Declaration__ ) __Declaration__ __attribute__((__packed__))
#endif

#ifdef _MSC_VER
#define PACK( __Declaration__ ) __pragma(pack(push, 1)) __Declaration__ __pragma(pack(pop))
#endif

PACK(struct S
{
  uint8_t  f0;
  uint32_t f1;
  uint32_t f2 : 17;
});

int main()
{
    printf("%zu\n", sizeof(struct S));
    return 0;
}

没有给出编译器选项。

输出:

gcc   (9.2.0): 8
clang (8.0.1): 8
cl    (19.23.28106.4 for x86): 9

为什么cl结果sizeof为 9?

标准是怎么说的?

4

2 回答 2

2

C 标准 C17 6.7.2.1/11 说,强调我的:

实现可以分配任何大到足以容纳位域的可寻址存储单元。如果有足够的空间剩余,紧跟在结构中另一个位域之后的位域将被打包到同一单元的相邻位中。如果剩余空间不足,则将不适合的位域放入下一个单元还是与相邻单元重叠是实现定义的。单元内位域的分配顺序(高位到低位或低位到高位)是实现定义的。未指定可寻址存储单元的对齐方式。

这些“存储单元”仅由编译器在内部知道,并且在不同的编译器上可以具有不同的大小。标准不支持位/字节“打包”的概念,并且从编译器到编译器的行为也不同。

您的测试中使用的编译器都没有违反 C 标准,因为这是 100% 实现定义的行为。

于 2020-03-20T11:17:24.483 回答
1

打包请求要求编译器将成员打包到结构中,没有填充。但是,它们不会更改成员本身的类型或表示。似乎 GCC 和 Clang 使用三个字节来表示一个 17 位的位域,而 Microsoft 使用四个字节,至少在基本类型是uint32_t. 因此,对于这种结构,Clang 和 GCC 将一个 1 字节对象、一个 4 字节对象和一个 3 字节对象打包成 8 个字节,而 Microsoft 将一个 1 字节对象、一个 4 字节对象和一个 4 -byte 对象分成九个字节。

这可能与 Microsoft 的编译器主要是 C++ 编译器以及C 和 C++ 对待位域的类型不同这一事实有关。在 C 中,位域的类型可能是实现定义的(标准并不完全清楚)。在 C++ 中,位域的类型是它的基类型。

我们可以通过考虑重新排列未打包的结构来测试这一点:

struct S
{
    uint8_t  f0;
    uint32_t f2 : 17;
    uint32_t f1;
};

GCC 和 Clang 显示sizeof(struct S)为 8 个字节,这与位字段的 3 个字节表示一致。MSVC 显示十二个字节,这与位域的四字节表示一致。

于 2020-03-20T10:55:33.463 回答