C语言sizeof空洞之字节对齐(#pragma pack使用)

本文详细解析了C编译器的字节对齐机制,包括默认字节对界条件、结构成员的对界规则及对齐方式,并通过实例展示了如何改变缺省对界条件。此外,文章还探讨了数据成员与结构整体的对齐规则,以及在包含子结构体情况下的对齐处理。

C编译器的缺省字节对齐方式(自然对界)


在缺省情况下,C编译器为每一个变量或是数据单元按其自然对界条件分配空间。

在结构中,编译器为结构的每个成员按其自然对界(alignment)条件分配空间。各个成员按照它们被声明的顺序在内存中顺序存储(成员之间可能有插入的空字节),第一个成员的地址和整个结构的地址相同。

 

C编译器缺省的结构成员自然对界条件为“N字节对齐”,N即该成员数据类型的长度。如int型成员的自然对界条件为4字节对齐,而double类型的结构成员的自然对界条件为8字节对齐。若该成员的起始偏移不位于该成员的“默认自然对界条件”上,则在前一个节面后面添加适当个数的空字节。

 

C编译器缺省的结构整体的自然对界条件为:该结构所有成员中要求的最大自然对界条件。若结构体各成员长度之和不为“结构整体自然对界条件的整数倍,则在最后一个成员后填充空字节。

例子1(分析结构各成员的默认字节对界条界条件和结构整体的默认字节对界条件):

复制代码
struct Test
{
char x1; // 成员x1为char型(其起始地址必须1字节对界),其偏移地址为0

char x2; // 成员x2为char型(其起始地址必须1字节对界,其偏移地址为1

float x3; // 成员x3为float型(其起始地址必须4字节对界),编译器在x2和x3之间填充了两个空字节,其偏移地址为4

char x4; // 成员x4为char型(其起始地址必须1字节对界),其偏移地址为8
};
复制代码


因为Test结构体中,最大的成员为flaot x3,因些此结构体的自然对界条件为4字节对齐。则结构体长度就为12字节,内存布局为1100 1111 1000。

例子2:

复制代码
#include <stdio.h>
//#pragma pack(2)
typedef struct
{
int aa1; //4个字节对齐 1111
char bb1;//1个字节对齐 1
short cc1;//2个字节对齐 011
char dd1; //1个字节对齐 1
} testlength1;
int length1 = sizeof(testlength1); //4个字节对齐,占用字节1111 1011 1000,length = 12

typedef
struct
{
char bb2;//1个字节对齐 1
int aa2; //4个字节对齐 01111
short cc2;//2个字节对齐 11
char dd2; //1个字节对齐 1
} testlength2;
int length2 = sizeof(testlength2); //4个字节对齐,占用字节1011 1111 1000,length = 12


typedef
struct
{
char bb3; //1个字节对齐 1
char dd3; //1个字节对齐 1
int aa3; //4个字节对齐 001111
short cc23//2个字节对齐 11

} testlength3;
int length3 = sizeof(testlength3); //4个字节对齐,占用字节1100 1111 1100,length = 12


typedef
struct
{
char bb4; //1个字节对齐 1
char dd4; //1个字节对齐 1
short cc4;//2个字节对齐 11
int aa4; //4个字节对齐 1111
} testlength4;
int length4 = sizeof(testlength4); //4个字节对齐,占用字节1111 1111,length = 8


int main(void)
{
printf(
"length1 = %d.\n",length1);
printf(
"length2 = %d.\n",length2);
printf(
"length3 = %d.\n",length3);
printf(
"length4 = %d.\n",length4);
return 0;
}
复制代码

改变缺省的对界条件(指定对界)
· 使用伪指令#pragma pack (n),C编译器将按照n个字节对齐。
· 使用伪指令#pragma pack (),取消自定义字节对齐方式。


这时,对齐规则为:

1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。

2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。

结合1、2推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。

 

因此,当使用伪指令#pragma pack (2)时,Test结构体的大小为8,内存布局为11 11 11 10。


需要注意一点,当结构体中包含一个子结构体时,子结构中的成员按照#pragma pack指定的数值和子结构最大数据成员长度中,比较小的那个进行进行对齐。例子如下:

#pragma pack(8)
struct s1{
short a;
long b;
};


struct s2{
char c;
s1 d;
long long e;
};
#pragma pack()

sizeof(s2)的结果为24。S1的内存布局为1100 1111,S2的内存布局为1000 1100 1111 0000 1111 1111。

例子:

复制代码
#include <stdio.h>
#pragma pack(2)
typedef
struct
{
int aa1; //2个字节对齐 1111
char bb1;//1个字节对齐 1
short cc1;//2个字节对齐 011
char dd1; //1个字节对齐 1
} testlength1;
int length1 = sizeof(testlength1); //2个字节对齐,占用字节11 11 10 11 10,length = 10

typedef
struct
{
char bb2;//1个字节对齐 1
int aa2; //2个字节对齐 01111
short cc2;//2个字节对齐 11
char dd2; //1个字节对齐 1
} testlength2;
int length2 = sizeof(testlength2); //2个字节对齐,占用字节10 11 11 11 10,length = 10


typedef
struct
{
char bb3; //1个字节对齐 1
char dd3; //1个字节对齐 1
int aa3; //2个字节对齐 11 11
short cc23//2个字节对齐 11

} testlength3;
int length3 = sizeof(testlength3); //2个字节对齐,占用字节11 11 11 11,length = 8


typedef
struct
{
char bb4; //1个字节对齐 1
char dd4; //1个字节对齐 1
short cc4;//2个字节对齐 11
int aa4; //2个字节对齐 11 11
} testlength4;
int length4 = sizeof(testlength4); //2个字节对齐,占用字节11 11 11 11,length = 8


int main(void)
{
printf(
"length1 = %d.\n",length1);
printf(
"length2 = %d.\n",length2);
printf(
"length3 = %d.\n",length3);
printf(
"length4 = %d.\n",length4);
return 0;
}
### 关于 `#pragma pack(push,1)` 的正确使用 `#pragma pack(push, n)` 和 `#pragma pack(pop)` 是一种用于控制结构体内存对齐的方式。通过这种方式可以临时修改编译器的字节对齐设置,并在完成后恢复原来的对齐方式。如果发现 `#pragma pack(push,1)` 未能成功取消结构体字节对齐,可能是由于以下几个原因: #### 原因分析 1. **未正确匹配 `push` 和 `pop`** 如果没有正确配对 `#pragma pack(push, n)` 和 `#pragma pack(pop)`,可能会导致后续代码仍然处于错误的对齐模式下[^3]。 2. **编译器行为差异** 不同的编译器可能对 `#pragma pack` 的实现略有不同。某些编译器可能存在特定的行为或限制,这可能导致预期效果无法达成[^4]。 3. **作用范围不明确** `#pragma pack(push, n)` 的作用仅限于它所在的源文件或者指定范围内。如果结构体定义超出了这个范围,则不会受到影响[^1]。 --- ### 解决方案 为了确保 `#pragma pack(push,1)` 能够正常工作并取消结构体字节对齐,请遵循以下方法: #### 方法一:严格配对 `push` 和 `pop` 确保每次调用 `#pragma pack(push, n)` 后都有对应的 `#pragma pack(pop)` 来恢复原始对齐方式。例如: ```cpp #pragma pack(push, 1) // 设置为1字节对齐 struct Test { char a; int b; }; #pragma pack(pop) // 恢复原对齐方式 ``` 这样能够保证在定义结构体期间应用了所需的对齐规则,而在其他地方保持原有的对齐设置[^3]。 #### 方法二:验证当前编译器支持情况 确认所使用的编译器是否完全支持 `#pragma pack(push/pop)` 功能。部分较老版本的编译器可能不具备完整的功能集。对于现代主流编译器(如 GCC、Clang 或 MSVC),通常都已提供良好的支持。 #### 方法三:检查全局影响 有时即使设置了局部的对齐选项,也可能受到更高层次上的全局配置干扰。可以通过显式声明全局默认值来排除这种可能性: ```cpp #pragma pack(8) // 设定全局最大对齐为8字节(假设) #pragma pack(push, 1) // 局部调整至1字节对齐 struct Example { char x; long y; }; #pragma pack(pop) // 结束后恢复正常 ``` 此操作有助于隔离潜在冲突因素的影响[^2]。 --- ### 示例代码展示 以下是基于上述原则的一个完整例子演示如何利用 `#pragma pack(push,1)` 实现精确控制结构体大小的功能。 ```cpp #include <iostream> // 开始记录初始状态前的状态 #pragma pack(push) // 切换到1字节边界对齐 #pragma pack(1) struct OneByteAlignedStruct { char ch; int integer; }; // 返回标准对齐设定 #pragma pack(pop) int main() { std::cout << "Size of OneByteAlignedStruct: " << sizeof(OneByteAlignedStruct) << " bytes." << std::endl; return 0; } ``` 运行以上程序应显示结果为5字节长度,表明确实采用了紧凑形式存储成员变量。 --- ### 注意事项 尽管减少内存消耗看似有利,但频繁切换字节对齐策略会增加维护复杂度以及降低性能优化机会。因此建议只针对必要场景才启用此类特性。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值