C中结构填充的假设

Posted

技术标签:

【中文标题】C中结构填充的假设【英文标题】:Assumption of structure padding in C 【发布时间】:2021-07-23 04:01:31 【问题描述】:

我在学习 C 中的结构填充时遇到了this video。

基本上它说如果我有一个结构

struct abc 
    char a;    // 1 byte
    char b;    // 1 byte
    int c;     // 4 bytes
 var;

那么,不要像这样存储结构体(c,...,c 表示 c 的四个字节;|| 是单词边界;_ 是字节的位置)

_  _  _  _ || _  _  _  _
a  b  c  c    c  c    

在b之后会填充两个字节的空白空间,结果是(e表示空)

_  _  _  _ || _  _  _  _
a  b  e  e    c  c  c  c

这样CPU可以在一个CPU周期内得到int c。

但是,这确实建立在 struct 的第一个成员(在我的例子中为 a)将在单词边界之后立即存储的假设之上。总是这样吗?

【问题讨论】:

你使用的是哪个编译器? 你的目标是哪个 CPU? 听起来你感兴趣的是结构的对齐要求。 我觉得重要的是要注意在这里起作用的不是单词边界,而是对齐要求。即使在一个单词中也可以有填充。例如,尝试一个以char 作为第一个元素,short 作为第二个元素的结构。 @Tony 我正在使用来自 Segger Embedded Studio 的默认 GCC。 【参考方案1】:

但是,这确实建立在 struct 的第一个成员将在字边界之后立即存储的假设之上。总是这样吗?

是的。

定义结构类型时,结构的对齐要求至少是其成员最严格的对齐要求。例如,如果一个结构具有对齐要求为 1 个字节、8 个字节和 4 个字节的成员,则该结构的对齐要求将是 8 个字节。定义结构时,编译器会自动计算出来。 (从技术上讲,C 标准可能允许编译器对结构进行更大的对齐——我没有看到任何反对它的规则——但实际上并没有这样做。)

然后,每当 C 实现为结构对象保留内存时(如定义该类型的对象,例如 struct foo x),它将确保内存按照该结构的要求对齐。这导致成员的对齐要求也得到满足。当程序使用malloc 分配内存时,返回的内存总是根据请求大小的任何对象对齐。

(如果你在程序中做了任何“有趣的事情”来为对象设置你自己的内存位置,例如将一个放在分配给malloc的内存中间,你有责任获得正确的对齐方式。)

此外,如有必要,结构将在末尾填充,以便其总大小是该对齐要求的倍数。然后,在这些结构的数组中,数组的每个连续元素也将从正确对齐的位置开始。

【讨论】:

谢谢!您能否详细说明“结构的对齐要求将至少是其成员最严格的对齐要求”?一个例子将不胜感激。 @jleng: 如果结构的成员有1字节、1字节、4字节、8字节、1字节和4字节的对齐要求,那么结构的对齐要求将为8字节,因为这是成员最严格的对齐要求。 @jleng:大多数平台要求多字节对象“对齐”,以便它们从 2 或 4 或 8 倍数的地址开始(取决于平台、类型和其他考虑)。如果struct 的成员必须对齐,使其地址为4 的倍数,则struct 对象本身也将对齐,使其地址为4 的倍数。 @JohnBode:谢谢。这消除了我留下的所有困惑。【参考方案2】:

结构类型的对象的地址总是等于对象的第一个成员的地址。

来自 C 标准(6.7.2.1 结构和联合说明符)

15 在结构对象中,非位域成员和单元 位域所在的地址按顺序增加 在其中声明它们。 指向结构对象的指针, 适当转换,指向其初始成员(或者如果该成员是 一个位域,然后到它所在的单元),反之亦然。 结构对象中可能有未命名的填充,但在其 开始。

这是一个演示程序

#include <stdio.h>

int main(void) 

    struct abc
    
        char a;
        char b;
        int c;
     abc =  'A', 'B', 3 ;
    
    printf( "&abc = %p, &abc.a = %p\n", ( void * )&abc, ( void * )&abc.a );
    
    struct abc *p = &abc;
    
    printf( "*( char * )p = %c\n", *( char * )p );
    
    return 0;

程序输出可能看起来像

&abc = 0x7ffe8cfad6c0, &abc.a = 0x7ffe8cfad6c0
*( char * )p = A

【讨论】:

【参考方案3】:

这是编译器进行的优化,因为它对 CPU 来说更容易。大多数编译器应该允许您禁用它。例如,在 GCC 中,您可以使用 __attribute__((packed))

另见How to override C compiler aligning word-sized variable in struct to word boundary。

【讨论】:

以上是关于C中结构填充的假设的主要内容,如果未能解决你的问题,请参考以下文章

在 C 中的结构中填充

C 结构中的自动字段重新排序以避免填充

为啥我们在套接字编程c中使用memset将结构填充为0?

用户代码可以安全地使用结构填充吗?

Python 和 C 结构之间的大小不匹配,默认结构对齐/填充

C 语言文件操作 ( 学生管理系统 | 命令行接收数据填充结构体 | 结构体写出到文件中 | 查询文件中的结构体数据 )