C和C++中的结构体对齐

63

最近一次面试中,我被问及有关C++结构体字段对齐的问题,并推测C和C++在结构体打包方面采用了相同的策略。

然而,这是错误的假设。面试官说,通常情况下,C和C++在结构体打包方面采用不同的方式,我们永远不应该期望相反的结果。在我看来,这是一个奇怪的陈述。在C++中,没有适用于双语C/C++头文件的pack "C"限定符。

因此,在实践中,这意味着您不能创建一个C++结构体并将其传递给C库,因为通常情况下,它的字段将以不同的方式对齐并具有不同的偏移量。但是,事实上,大多数程序员严重依赖此互操作性,直到将指向C POD结构的指针转换为C++包装器周围的引用,并带有一些辅助方法。请您澄清这个问题吗?


13
这是个奇怪的问题。如果C和C++之间的结构填充不同,我就会陷入困境。也许面试官在某个不常见的编译器中找到了一些非常奇怪的陷阱--或者他在思考C和C++编译器可能完全不同,并期望两个编译器以相同的方式对齐字段并具有相同的填充,这可能不是我们应该过于自信依赖的东西(但即使在两个C编译器或两个C++编译器之间也是如此--与语言几乎无关)。 - user4842163
17
你是正确的。打包是处理器实现细节,它确保遵守对齐规则。针对同一处理器的 C 编译器和 C++ 编译器必须处理相同的实现细节。 - Hans Passant
4
@ISanych,什么?g++/gcc只是前端。在编译过程中,文件扩展名决定了实际的驱动程序名称,你使用什么名称并不重要(链接时则不同)。 - SergeyA
6
由于C语言中不支持继承,因此在C++中相同的结构体不需要使用RTTI或虚表指针。 - Sam Cristall
2
@SergeyA:填充是对齐要求的结果,这就是为什么我使用斜杠而不是和号。通常情况下,您不会在没有实际需要的情况下进行填充。CPU 可能无法使用单个指令访问其本机类型。例如,对于某些 ARM CPU,gcc 会为 packed struct 发出特殊代码,该代码按字节读取 int 并将它们组装到寄存器中(反之亦然)。与手动(反)序列化相同。而且 pack 指令并不标准,也不被所有编译器支持。 - too honest for this site
显示剩余14条评论
3个回答

68

C和C++语言标准都没有要求结构体填充,而是将其留给编译器实现的细节。严格解释意味着在两者之间没有保证结构体是相同的。

然而,在实践中,一个能够同时支持C和C++的工具链(如GCC或Clang)的特定版本可以以相同的方式打包相同的结构体,如果需要的话。否则,世界上许多生产代码就不能正常工作。这是由工具链提供的保证,而不是语言本身。

值得注意的是,如果您声明类似于C原始结构的结构,但添加了访问说明符(privatepublicprotected),那么布局会发生变化,但这有点牵强,因为结构体不再是相同的。


1
是的,如果您添加虚函数,它也会有所不同。类似是相似的。 - SergeyA
4
当我创建一个 C 的 .h 文件时,我会使用 #ifdef __cplusplusextern "C" ... 来确保 C++ 能够以与 C 兼容的方式解释其中的内容。 - Craig Estey
4
你可能正在考虑的是extern "C" int myfnc(return 0;),但extern "C" { struct mystruct { int y; int z; }; }可以保证特定工具链的对齐方式。这告诉C++ 不要 插入vtable指针,并使用C的对齐方式[如果它_不同_]。此外,C++将 不会mystruct定义为一个类型[如果这样做,很多C .h文件就会出问题]。大多数工具链都希望进行互操作(例如gcc和clang, 可能是MS),所以在那里也适用。这与计算机体系结构(例如x86、arm、IBM / 370)以及对齐的特定硬件限制有关。 - Craig Estey
1
换句话说,使用extern "C"是否有一种保证结构体对齐的效果(不仅仅是链接器符号),而不会涉及编译器错误?也许我过于关注编译器通常做什么,而忽略了标准。 - user4842163
5
没有编译器错误。 extC块在其中保证了C语义:没有名称混淆(!),结构对齐到C的版本[_如果_不同的话--依我看,面试官认为它们即使没有extC也不匹配,是错误的想法],并且不定义类型。这是“未来证明”的。我们都困扰于此,因为某个随机的面试官[可能是一个无知的LL]说了一些废话,而我们被留下来清理残局。已知工具所做的事情,除了规范之外,是符合常识的事情。否则,很多程序会崩溃。这是一个架构问题(例如,神话般的架构需要16字节对齐)。 - Craig Estey
显示剩余7条评论

29
这显然是错误的(面试官方面)。任何使用处理结构体的低级API的人都清楚,C和C ++的结构体打包方式相同。所有这些都是接受'C'结构体的C函数,但它们每天从C ++代码中安全地调用数百万次。你应该庆幸有这个问题。这表明你不应该在那里工作。

9
我喜欢这个答案(虽然我也喜欢另一个答案),因为它实用而且值得安慰。它的确听起来像面试官脑海中有这种奇怪的概念,而不是试图让人们以这种超级理论化的方式思考语言标准的假设 - 这主要是因为这一部分:“C和C ++以不同的方式打包结构体,我们永远不应该期望相反。”这听起来根本不像面试官牢牢掌握自己在谈论什么。这是我预期从一家公司的面试问题中看到的那种类型的问题... - user4842163
基于迷信的奇怪编程标准。至少我见过那种类型。也许我太苛刻了。 - user4842163
5
是的,对于一个错误的问题,并没有好的答案,应该庆幸自己没有在那里工作。 - Marek Waszkiewicz
哇,有人刚刚批量地给我所有的答案点了踩。干得好! - SergeyA
@SergeyA:面试官不喜欢你的答案。 - user276648

4
当C++语言被开发出来时,开发者们发现C程序员使用了一些C++开发者不想保证的东西。然而不保证这些东西会导致很多C代码无法在作为C++代码运行时正常工作。这是不可取的。因此他们发明了“POD”结构:一个结构体没有使用任何C++特性,在C++程序中的行为与在C程序中完全相同(除了实现定义的行为可能会改变,因为C编译器和C++编译器显然不是同一种实现。另一方面,C++编译器可能只是从C编译器复制实现定义)。
如果你拿任何一个作为有效C++结构体的普通C结构体(例如没有成员命名为“class”),然后只需在左大括号后面添加“public:”,那么它的布局、成员顺序、对齐等都可以改变。即使所有结构体成员默认都是公有的,所以实际上什么也没有改变。但由于“public:”,它就不再是POD了。

你认为呢?当然了,一个具有所有公共成员(以及所有私有成员)的类的例子是POD没问题的。 - SergeyA

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