字对齐之 sizeof和pragma pack 的用法

本文详细解析了C/C++中结构体的内存对齐规则及#pragmapack指令的使用方法,并通过具体示例说明了不同情况下结构体大小的计算方式。

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

面试常问的几道题

1.     sizeof#pragma pack 用法

(注:以下用法都以windows平台下32位机为标准。)

sizeof#pragma pack 的用法经常会在面试或笔试中问到,估计很多人也和我一样,答错的几率很大。究其原因,还是不明白原理。有时候只是凭着笔试前的记忆乱猜的。

首先,我们看sizeof的用法。

对于结构体:

struct Data1

{

           char  m_ch;

           int   m_nData;

};

sizeof(Data1)=?

a)       可能有人会回答:sizeof(Data1) = 1 + 4=5很明显,这类人肯定是连字对齐是什么都不知道。

b)      也有人会这么回答:sizeof(Data1) = 2 + 4=6这类人可能知道什么是字对齐,但是不明白到底字对齐是啥意思。可能认为字对齐就是以2的整数倍对齐。结果面试官一问为什么,想当然的就说出了自己的原因,【说32位机器的字对齐方式是这样的,结构体中每个字段的大小都必须是2的整数倍,因为这样CPU寻址会快】,并毫不犹豫的认为这种说法很完美。但实际上,这只是答对了一点皮毛。很悲剧,我就是属于这类人,只知道有这么回事,但并不知道怎么回事。

经过面试官的指点与自己google出来的结果,我进行了一些总结。我们还是从上面的例子开始讲起。这里先给个链接,我当中有很大一部分是根据这个总结而来的。

http://www.cnblogs.com/bingxuefly/archive/2007/11/12/957056.html

n  Windows平台下,默认的字对齐方式遵循以下2条规则:

条款1    成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的

型所占用的字节数的倍数

也就是说,要遵循这么个法则。

------------------------------------------------------------------------------

成员变量类型 | 对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

 

char              | 偏移量必须为sizeof(char)       1的倍数

short         | 偏移量必须为sizeof(short)    2的倍数

int                 | 偏移量必须为sizeof(int)         4的倍数

float              | 偏移量必须为sizeof(float)     4的倍数

long              | 偏移量必须为sizeof(long)      4的倍数

double          | 偏移量必须为sizeof(double), 即8的倍数

__int64          | 偏移量必须为sizeof(__int64), 即8的倍数

…….

条款2     各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。

条款3     整个结构体的大小必须是结构体中所占空间最大的那个类型所占用字节数的整数倍。

分析:据这两条规则,对于结构体Data1,我们做如下分析。

【注意,偏移大小从0开始,也就是说,结构体的第一个字节我们认为是偏移为0

首先,编译器会为char m_ch分配空间,其起始地址跟结构的起始地址相同,根据条款1,此时相对偏移为0,正好为sizeof(char)=1的倍数。然后为int m_nData分配空间,由于它的相对偏移要是sizeof(int)=4的倍数,显然,其相对偏移就是4了,那么,根据条款2,相对偏移为123的字节就应该空着,里面什么内容也不被填充。

再来看条款3满不满足,此时结构体的大小已为8个字节,恰好是结构体里面最大的数据段【int m_nData】大小的整数倍。

所以,整个数据的大小就为sizeof(Data1) = 1+3+4=8;

也就是sizeof(Data1) = sizeof(char) + 自动对齐的3个字节 + sizeof(int) = 8;

再来分析一个更加复杂的结构:

struct Data

{

           __int64  m_64n1;

           short    m_sh1;

           __int64  m_64n2;

           short    m_sh2;

           char     m_ch;

           short    m_sh3;

};

第一步,编译器会为__int64 m_64n1分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(__int64)=8的整数倍,此时相对偏移为0【也就是结构体的起始位置】,正好是8的整数倍。此字段占用的字节为第0~7个字节。

第二步,编译器会为short m_sh1分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(short)=2的整数倍,根据第一步分配后的空间来看,此时相对偏移已经移动到第8个字节,正好是其整数倍。此字段占用的字节为第8~9个字节。

第三步,编译器会为__int64 m_64n2分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(__int64)=8的整数倍,根据第二步分配后的空间来看,此时相对偏移已经移动到第10个字节,不是其整数倍。那么此时就要根据条款2,将偏移为第10~15的位置填充。此时偏移为16,正好是8的整数倍。此字段占用的字节为第16~23个字节。

第四步,编译器会为short m_sh2分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(short)=2的整数倍,根据第三步分配后的空间来看,此时相对偏移已经移动到第24个字节,正好是其整数倍。此字段占用的字节为第24~25个字节。

第五步,编译器会为char m_ch分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(char)=1的整数倍,根据第四步分配后的空间来看,此时相对偏移已经移动到第26个字节,正好是其整数倍。此字段占用的字节为第26个字节。

第六步,编译器会为short m_sh3分配空间,根据条款1,该成员变量的相对偏移应该是sizeof(short)=2的整数倍,根据第二步分配后的空间来看,此时相对偏移已经移动到第27个字节,不是其整数倍。那么此时就要根据条款2,将偏移为第27的位置填充。此时偏移就为28了,正好是2的整数倍。此字段占用的字节为第28~29个字节。

此时,整个结构体大小就为30,那么,根据条款3,整个结构体中最大的成员类型(或者是成员变量)的大小为8,因此,结构体最终大小应该为32,第30~31个字节自动被填充。

可以定义一个结构体,看一下其内存结构:

Data5 data5 = {1, 2, 3, 4, 5, 6};

01 00 00 00 00 00 00 00 02 00 cc cc cc cc cc cc 03 00 00 00 00 00 00 00 04 00 05 cc 06 00 cc cc

 0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 24  25 26 27 28 29 30 31

上面的一行为对应偏移。


现在,我们看#pragma pack的用法了。

使用格式:

#pragma pack(push)   // 保存对齐状态

#pragma pack(4)      // 设定为4字节对齐

struct Data4

{

    char   m_ch;

    short  m_sh;

    double m_nData;

};

#pragma pack(pop)    // 恢复对齐状态

这里我直接把人家的抄过来了,人家写得很清楚了。

#pragma pack(n)来设定变量以n字节对齐方式。

ü  n字节对齐就是说变量存放的起始地址的偏移量有两种情况:

第一,  如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式;

第二,  第二,如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。

ü  结构的总大小也有个约束条件,分下面两种情况:

如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

需要注意的一个问题是 n的大小只能为为124816,如果写其它的,编译器会报出警告【warning C4086: expected pragma parameter to be '1', '2', '4', '8', or '16'

 

好了,我们来分析一下结构体的大小如何求得。

第一步,编译器会为char m_ch分配空间,根据n=4,而sizeof(char)=1,那么偏移量要满足默认对齐方式,此时相对偏移为0,正好是其的整数倍。此字段占用的字节为第0个字节。

第二步,编译器会为short m_sh分配空间,根据n=4,而sizeof(short)=1,那么偏移量要满足默认对齐方式,此时相对偏移为1,不是其的整数倍。根据先前默认的方式,需要将相对偏移为第1个字节的位置填充起来,然后再为该字段分配空间。此字段占用的字节为第2~3个字节。

第二步,编译器会为double m_nData分配空间,根据n=4,而sizeof(double)=8n<8,那么偏移量只要是4的倍数就可以了,此时相对偏移为4,刚好是n的整数倍。所以,此字段占用的字节为第4~11个字节。

【再次提醒一下,占用字段的字节我们按照第0个开始计算,也就是说,如果一个结构体占用32个字节,那么我们数数的时候为第0到第31个字节来算;这里的偏移也是从0开始计算】

 

还有一些重要的信息我也抄过来了。

sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,

第一,结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。

第二,没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一个实例在内存中都有唯一的地址。

参考原文链接:http://www.cnblogs.com/bingxuefly/archive/2007/11/12/957056.html

### 什么是 `#pragma pack`? `#pragma pack` 是 C/C++ 中的一个编译器指令,用于控制结构体成员的内存对齐方式。默认情况下,编译器会根据目标平台的架构性能优化需求,自动选择一个合适的对齐值[^4]。然而,在某些特定场景下(如嵌入式开发或网络协议数据包解析),可能需要手动调整结构体的对齐方式以节省内存或满足外部数据格式要求。 --- ### `#pragma pack` 的基本语法 #### 1. 设置对齐方式 ```c #pragma pack(n) ``` - 参数 `n` 表示指定的对齐节数,必须是 1、2、4、8 或 16。 - 此指令生效后,结构体成员将按照不超过 `n` 节的方式对齐。 #### 2. 恢复默认对齐方式 ```c #pragma pack() ``` - 取消自定义的对齐设置,恢复到编译器默认的对齐规则[^4]。 #### 3. 保存与恢复对齐状态 ```c #pragma pack(push, n) // 保存当前对齐状态,并设置新的对齐方式 #pragma pack(pop) // 恢复之前保存的对齐状态 ``` - `push` `pop` 配合使用,可以确保局部修改对齐方式时不会影响其他代码部分[^1]。 --- ### 示例代码分析 以下是一个完整的例子,展示如何使用 `#pragma pack` 控制结构体对齐: ```c #include <stdio.h> #pragma pack(push, 4) // 保存当前对齐状态,并设置为 4 节对齐 struct CC { double d; // 8 节 char b; // 1 节 int a; // 4 节 short c; // 2 节 }; #pragma pack(pop) // 恢复之前的对齐状态 struct BB { double d; // 8 节 char b; // 1 节 int a; // 4 节 short c; // 2 节 }; int main(void) { printf("Size of struct CC: %zu bytes\n", sizeof(struct CC)); printf("Size of struct BB: %zu bytes\n", sizeof(struct BB)); return 0; } ``` #### 输出结果及解释 假设默认对齐方式为 8 节: - **`struct CC`**:在 `#pragma pack(4)` 的作用下,所有成员按不超过 4 节对齐。 - `double d` 占 8 节,但由于对齐限制,实际占用 8 节。 - `char b` 占 1 节,后面填充 3 节。 - `int a` 占 4 节。 - `short c` 占 2 节,后面填充 2 节。 - 总大小为 16 节。 - **`struct BB`**:未受 `#pragma pack` 影响,按默认 8 节对齐。 - 结果为 24 节。 --- ### 常见用法总结 1. **节省内存** - 在嵌入式系统中,由于硬件资源有限,可以通过 `#pragma pack(1)` 禁止任何对齐填充,从而最大限度地减少结构体的内存占用[^2]。 2. **兼容外部数据格式** - 当处理网络协议或文件格式时,数据的存储顺序对齐方式通常由协议规定。使用 `#pragma pack` 可以确保结构体布局与外部数据一致[^5]。 3. **临时修改对齐方式** - 使用 `#pragma pack(push, n)` `#pragma pack(pop)`,可以在不影响全局代码的情况下,仅对局部代码进行对齐方式调整。 --- ### 注意事项 - **性能问题**:非自然对齐(如 `#pragma pack(1)`)可能导致 CPU 访问内存时效率降低,尤其是在某些架构上[^4]。 - **跨平台兼容性**:不同编译器对 `#pragma pack` 的实现可能存在细微差异,建议在使用前查阅相关文档[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值