在 C 中的结构中填充
Posted
技术标签:
【中文标题】在 C 中的结构中填充【英文标题】:Padding in structures in C 【发布时间】:2011-10-21 13:26:35 【问题描述】:这是一道面试题。到现在为止,我曾经认为这些问题完全取决于编译器,不应该让我担心,但现在我很好奇。
假设你有两个结构:
struct A
int* a;
char b;
和,
struct B
char a;
int* b;
那么你更喜欢哪一个,为什么? 我的回答是这样的(尽管我有点在黑暗中拍摄),应该首选第一个结构,因为编译器为结构分配空间的字大小的倍数(这是指针的大小 - 32 上的 4 个字节)位机器和 64 位机器上的 8 个字节)。因此,对于这两种结构,编译器都会分配 8 个字节(假设它是 32 位机器)。但是,在第一种情况下,填充将在我的所有变量之后完成(即在 a 和 b 之后)。因此,即使有机会 b 得到一些溢出并破坏我的下一个填充字节的值,但我的 a 仍然是安全的。
他似乎不太高兴,并要求第一个结构比第二个结构有一个缺点。我没什么好说的。 :D
请帮我解答。
【问题讨论】:
我能想到的唯一一点好处是,如果您稍后添加更多 char 字段,那么对于第一个,您可以在最后添加它们而无需引入额外的填充。除此之外,在我看来,看看你是否有足够的信心坚持没有优势是一个棘手的问题。 我不确定我理解你所说的“溢出并破坏我的下一个填充字节”是什么意思。您是否担心堆栈溢出,或者只是超出了 char 的容量? @chris:如果你超过了 char 的容量,它只会绕圈子。我的意思是缓冲区溢出。我实际上并没有考虑太多。:D 我猜这可能与机器字中字符的位置有关。 C 规范不保证 char 将映射到内存字的第一个字节,因此它也可以映射到机器字的最后一个字节,从而为任一结构创造优势。 【参考方案1】:我认为这些结构中的任何一个都没有优势。这个等式中有一个(!)常数。保证结构成员的顺序与声明的一致。
因此,在以下情况下,第二个结构可能具有优势,因为它可能具有较小的尺寸,但在您的示例中不是,因为它们可能具有相同的尺寸:
struct
char a;
int b;
char c;
X;
对比
struct
char a;
char b;
int c;
Y;
下面是关于 cmets 的更多解释:
以下所有内容都不是 100%,而是在 int 为 32 位的 32 位系统中构造结构的常见方式:
结构 X:
| | | | | | | | | | | | |
char pad pad pad ---------int---------- char pad pad pad = 12 bytes
结构 Y:
| | | | | | | | |
char char pad pad ---------int---------- = 8 bytes
【讨论】:
是的,这就是@kevin beck 在他的评论中暗示的... :) 请您解释一下为什么第一个尺寸会更小..我在编译器中找到了其他方法.. @Shashi Bhushan - 你说得对,我打错字了,我写错了,第二个更小,我的错... @MByD:他的第二个可能一样大,因为最后会有填充,所以当数组中有两个或更多时,成员仍然会自然对齐界限,而不仅仅是第一个界限。 @MByD:还有一个保证:在第一个成员之前不能有填充。【参考方案2】:当值与某个边界对齐时,某些机器 access data more efficiently。需要对齐一些require 数据。
在现代 32 位机器上,如 SPARC 或 Intel [34]86,或任何 摩托罗拉芯片从 68020 起,每个数据项通常必须是 ``self-aligned'',从一个地址是其倍数的地址开始 类型大小。 因此,32 位类型必须以 32 位边界开始,16 位 16 位边界上的类型,8 位类型可以在任何地方开始, struct/array/union 类型具有它们最严格的对齐方式 会员。
所以你可以拥有
struct B
char a;
/* 3 bytes of padding ? More ? */
int* b;
在“自对齐”情况下最小化填充的简单规则(以及 对大多数其他人没有害处)是通过以下方式订购您的结构成员 缩小尺寸。
与第二个相比,我个人认为第一个结构没有缺点。
【讨论】:
【参考方案3】:在这种特殊情况下,我想不出第一个结构相对于第二个结构的劣势,但可以举出一些例子,说明将最大成员放在首位的一般规则存在劣势:
struct A
int* a;
short b;
A(short num) : b(2*num+1), a(new int[b])
// OOPS, `b` is used uninitialized, and a good compiler will warn.
// The only way to get `b` initialized before `a` is to declare
// it first in the class, or of course we could repeat `2*num+1`.
我还听说过一个相当复杂的大型结构案例,其中 CPU 具有用于访问指针+偏移量的快速寻址模式,用于小偏移量值(例如,最多 8 位,或其他立即值限制) )。您最好通过将尽可能多的最常用字段放在最快指令的范围内来对大型结构进行微优化。
CPU 甚至可能对指针+偏移量和指针+4*偏移量进行快速寻址。然后假设您有 64 个 char 字段和 64 个 int 字段:如果您将 char 字段放在首位,那么两种类型的所有字段都可以使用最佳指令进行寻址,而如果您将 int 字段放在首位,那么 char 字段不是 4 -aligned 只需要以不同的方式访问,可能通过将常量加载到寄存器而不是立即值,因为它们超出了 256 字节的限制。
从来不需要自己做,例如 x86 无论如何都允许大的立即值。这不是任何人通常会考虑的那种优化,除非他们花费大量时间盯着组装。
【讨论】:
【参考方案4】:简而言之,在一般情况下选择任何一个都没有优势。在实践中唯一重要的选择是启用结构打包,在这种情况下struct A
将是更好的选择(因为两个字段将在内存中对齐,而在struct B
b
字段将位于奇数偏移处)。结构打包意味着结构内部不插入任何填充字节。
但是,这是一种相当少见的情况:结构打包通常仅在特定情况下启用。这不是大多数程序的问题。而且它也不能通过 C 标准中的任何可移植结构来控制。
【讨论】:
+1,问题中没有给出关于对齐的假设/约束,所以这是一个合理的论点。【参考方案5】:这也是一种猜测,但大多数编译器都有一个未对齐选项,它不会明确添加填充字节。然后,这需要(在某些平台上)运行时修复(硬件陷阱)来动态对齐访问(具有相应的性能损失)。如果我没记错的话,HPUX 就属于这一类。因此,即使使用了未对齐的编译器选项,第一个结构字段仍然对齐(因为正如您所说,填充将在末尾)。
【讨论】:
虽然大多数编译器确实有未对齐结构的选项,但通常它们不会使用陷阱 - 将逐字节复制到堆栈,然后从那里对齐读取会更便宜。此外,在某些平台(尤其是 x86)上,可以在本机以最小的开销完成未对齐的访问(但是有 一些 开销,但没有陷阱,以及 SMP 上的读/写撕裂问题)。跨度>以上是关于在 C 中的结构中填充的主要内容,如果未能解决你的问题,请参考以下文章
C 语言文件操作 ( 学生管理系统 | 命令行接收数据填充结构体 | 结构体写出到文件中 | 查询文件中的结构体数据 )