关于sizeof与#pragma pack

本文详细解析了结构体在C/C++中的内存对齐规则,包括三个基本原则,并通过五个实例深入浅出地展示了不同场景下如何应用这些规则。此外,还介绍了VC编译器中的#pragmapack指令及其对内存对齐的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

对于结构体,在使用sizeof的时候会进行字节的对齐,对齐的规则如下:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
   备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。

2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
  备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。

3)结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。
  备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。

按照上面的规则,对如下几个例子进行分析:
例1:

1 struct  DATA1
2 {
3    char    c1; //偏移量0,累积size = 1
4    char    c2; //偏移量1,累积size = 1 + 1 = 2
5    short    si; //偏移量2,累积size = 2 + 2
6}
;

这个例子中,首先找到结构体变量的首地址,按照规则1,该首地址需要能被最宽基本类型成员整除,即能被short(字节数2)整除。接下来,为c1分配内存,由于其占一个字节,此时内存状态为*,然后,为c2分配内存,由于结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,而c2是char型成员,占用一个字节,刚好能够对齐,此时内存状态为**,最后,为short型成员分配内存,由于前两个成员占2个字节,si相对于结构体首地址的偏移量刚好是2的整数倍,所以,此时内存状态为****

例2:

1 struct  DATA2
2 {
3    char    c1; //偏移量0,累积size = 1
4    short    si; //偏移量1 + (1),累积size = 1 + (1) + 2 = 4
5    char    c2; //偏移量4,累积size = 4 + 1 = 5,但按最大长度sizeof(short) = 2对齐,故最后取6
6}
;

这个例子中,首先找到结构体变量的首地址,与上例同。接下来,为c1分配内存,由于其占一个字节,此时内存状态为*,然后,为si分配内存,由于结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,而si是short型成员,占用2个字节,而前一个成员占用一个字节,所以需要在c1与si之间补一个字节进行对齐,此时内存状态为*×**,最后,为char型成员分配内存,由于前两个成员占4个字节,c2相对于结构体首地址的偏移量是1的整数倍,所以,此时内存状态为*×***,接下来,按照规则3,结构体的总大小为结构体最宽基本类型成员大小的整数倍,所以总大小应该是short(2字节)型变量大小的整数倍,需要再补一个字节,即*×***×,总共占6个字节

例3:

1 struct  DATA3
2 {
3    char    c1; //偏移量0,累积size = 1
4    double    d; //偏移量1 + (7),累积size = 1 + (7) + 8 = 16
5    char    c2; //偏移量16,累积size = 16 + 1 = 17,但按最大长度sizeof(double) = 8对齐,故最后取24
6}
;



关于#pragma pack(n)
VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;否则必须为n的倍数。

例4:

 1 #pragma  pack(push)  // 保存对齐状态
 2 #pragma  pack(2) // 设定为4字节对齐
 3 struct  DATA4
 4 {
 5    char    c1;
 6    double  d1;
 7    int    i1;
 8    short  s1;
 9}
;
10 #pragma  pack(pop) // 恢复对齐状态

以上结构的大小为16,下面分析其存储情况,首先为c1分配空间,其偏移量为0,c1占用1个字节,由于1<2,所以按照char类型变量默认的对其方式。接着开始为d分配空间,这时其偏移量为1,需要补足1个字节,这样使偏移量满足为n=2的倍数(因为sizeof(double)大于n),d占用8个字节。接着为i1分配空间,这时其偏移量为10,满足为2的倍数,i1占用4个字节。这时已经为所有成员变量分配了空间,共分配了14个字节。 接下来再为s1分配空间,由于min(sizeof(s1),2)=2,所以s1按照2字节对齐,此时已经是对其的,所以分配2字节内存,这时,总共占用16字节

例5:

 1 #pragma  pack(push)  // 保存对齐状态
 2 #pragma  pack(4) // 设定为4字节对齐
 3 struct  DATA4
 4 {
 5    char    c1;
 6    double  d;
 7    int    i1;
 8    short  s1;
 9}
;
10 #pragma  pack(pop) // 恢复对齐状态

以上结构的大小为20,下面分析其存储情况,首先为c1分配空间,其偏移量为0,c1占用1个字节,由于1<2,所以按照char类型变量默认的对其方式。接着开始为d分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),d占用8个字节。接着为i1分配空间,这时其偏移量为12,满足为4的倍数,i1占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节。接下来再为s1分配空间,由于min(sizeof(s1),4)=2,所以s1按照2字节对齐,此时已经是对齐的,所以分配2字节内存,这时,总共占用16字节,由于占用内存总数需要为4的整数倍,所以结构大小为20

【参考文献】
1.《对几组sizeof信息的分析http://blog.vckbase.com/billdavid/archive/2004/06/23/509.html
2.《sizeof(结构体)和内存对齐

http://www.ksarea.com/articles/20071004_sizeof-struct-memory.html

转载于:https://www.cnblogs.com/whu-gy/archive/2008/05/04/1182365.html

`#pragma pack(n)` 是 C 语言中用于控制结构体成员内存对齐方式的一种预处理指令,主要用于减少结构体占用的内存空间或满足特定硬件对内存对齐的要求。它通常在嵌入式系统、协议解析、文件格式解析等场景下使用。 --- ## ✅ `#pragma pack(n)` 的作用 默认情况下,编译器为了提高访问效率会对结构体成员进行**字节对齐(padding)**。例如,在 32 位系统中,int 类型可能会按 4 字节对齐,char 按 1 字节对齐,short 按 2 字节对齐等。 `#pragma pack(n)` 指令告诉编译器将结构体成员的对齐方式设置为不超过 n 字节。n 可以是 1、2、4、8 等常见值。如果 n 小于结构体成员自然对齐大小,则强制按 n 对齐;如果大于,则仍按自然对齐方式对齐。 --- ## ✅ 示例说明: ```c #include <stdio.h> #pragma pack(push, 1) // 设置为 1 字节对齐 struct MyStruct { char a; // 1 byte int b; // 4 bytes short c; // 2 bytes }; #pragma pack(pop) // 恢复之前的对齐方式 int main() { printf("Size of struct: %lu\n", sizeof(struct MyStruct)); return 0; } ``` ### 输出结果: ``` Size of struct: 7 ``` ### 解释: - `char a` 占用 1 字节; - `int b` 原本应占 4 字节并按 4 字节对齐,但由于使用了 `#pragma pack(1)`,所以紧跟在 `a` 后面,没有填充; - `short c` 原本需要 2 字节对齐,但同样因为 `pack(1)`,直接放在 `b` 后面; - 总共占用 `1 + 4 + 2 = 7` 字节。 如果不加 `#pragma pack(1)`,默认对齐下结构体大小可能是 12 或者 8(取决于平台和编译器)。 --- ## ✅ 使用技巧 ### 保存和恢复当前对齐状态: ```c #pragma pack(push) // 保存当前对齐方式 #pragma pack(1) // 设置新的对齐方式 struct PackedStruct { char a; int b; }; #pragma pack(pop) // 恢复之前保存的对齐方式 ``` 这样可以避免影响其他结构体的对齐方式。 --- ## ✅ 注意事项 - `#pragma pack(n)` 是非标准 C 特性,不同编译器可能支持程度不同。 - 在跨平台项目中使用时要小心,建议配合宏定义来屏蔽差异。 - 使用不当可能导致性能下降,甚至在某些平台上引发总线错误(如 ARM 架构不支持非对齐访问)。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值