内存对齐----#pragma pack的用法及大小的计算

现在的一些处理器,需要你的数据的内存地址必须是对齐(align)的,即使不是必须,如果你对齐的话,运行的速度也会得到提升。虽然对齐会产生的额外内存空间,但相对于这个速度的提升来说,是值得的。

所谓对齐,就是地址必须能整除一个整数,这个就是对齐参数(alignment value)。合法的取值范围是1、2、4、6、16、……、8192。

怎样对齐呢?编译器帮你搞定。

怎样设置编译器的对齐方式呢?用#pragma pack( n )和__declspec(align(#))。

依据它俩,编译器是咋工作的?这个就是接下来要说的了。

#include <stdio.h>
#pragma pack( 1 )
struct A
{             
    char a;
    short b;
    char c;
};

int main()
{
    printf("%d\n",sizeof(A));
    return 0;
}

 

OK,下面对这个代码进行详细的分析。

 

用MSDN的话一言以蔽之:

“The alignment of a member (except the first one) will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.”

翻译成中文,也就是:

“结构体中的数据成员,除了第一个是始终放在最开始的地方,其它数据成员的地址必须是它本身大小或对齐参数两者中较小的一个的倍数。”

 

P.S:注意上面所说的后面一句话,也就是说,结构体的数据成员的地址必须是本身大小和对齐参数中较小的那一个。

 

 

(1)在pack为1的时候,对齐参数是1,那么我们对这个结构体每一元素进行分析。

 

char a;        //    第一个元素在[0]位置处

 short b; //short两个字节,地址是min(1,sizeof(short))的倍数,即1的倍数[1~2]

 char c; // 地址应该是min(1,sizeof(1))的倍数,从而即为[3]

 

故在pack为1的时候,输出的结果应该是4([0~3]),其中所有的元素都填满了。

 

 

(2)在pack为2的时候,同样按照上面的方法,我们继续来分析下。

Char a; //第一个占[0]位置。

Short b; //min(2,sizeof(short)),也就是必须为2的倍数,从而[2~3]

Char c;//min(2,sizeof(char)),也就是位1,地址为[4]

因此最后占据的大小是[0],[2~3],[4],整个结构体的大小size必须是2的倍数,所以应该是6(向上对齐至2的倍数)

 

 

(3)在pack为4的时候,同上,得到的结果是

[0],[2~3],[4],因此也是6.

 

 

然后我们对上面的这个结构体变换一下顺序,可以得到。

 

struct B

{

         char a;

         char b;

         short c;

};

 

 

在#pragma pack(4)的情况下,输出却是4(注:上面的输出时6)

解释如下:

 

Char a;//占据一个字节,地址为【0】

Char b;//地址应该是min(4,sizeof(char)) = 1的倍数,也就是地址为【1】

Short c; //地址应该是min(4,sizeof(short)) = 2的倍数,也就是【2~3】

故总体占据的是【0~3】的连续单元,也就是4.

 

 

至此,我们对#prgama pack(n)的用法和对应的判定方法有了一个全新的认识。



 

特别提出:
sizeof(ao.a )还是1,sizeof(ao.b )还是2。

 如果struct B中含有A的一个对象m_a,
struct B
{
   …
   A m_a;
   …
}
则这个m_a对齐参数是A中最大的数据类型的大小(这里是short的2)和n中较小者。如果这个对齐参数是B中最大的话,最后B的大小也会与这个对齐参数有关。


 

m_a的对齐参数,由于是A的变量,所以采用A的对齐参数,也就是应该是A的最大元素个数和n中较小的值。而B的大小就要根据这个对齐参数来确定大小。


 

#include <iostream>
#include <stdlib.h>

#define NUM 1
using namespace std;

#pragma pack ( 16 )

typedef struct {
    int a;
    char b;
    double c;
}test;

struct B
{
    int a;
    test b;
};
int main()
{
    cout << "sizeof(int) = "<<sizeof(int) << endl;
    cout << "sizeof(char) = " << sizeof(char) << endl;
    cout << "sizeof(double) = " << sizeof(double) << endl;
    cout << sizeof(test)<< endl;
    cout << sizeof(B) << endl;
    system("PAUSE");
    return 0;
}


 

(1)在pack为1的时候,由于min中有一个为1,所以都是相邻存放的。

Sizeof(test)就是int+char+double的大小之和,即13.

而对应的sizeof(B)则是一个int和一个struct之和。Int占4B,而struct的对齐参数则是

Min(1,sizeof(max(A)),A中最大的元素师double类型的,也就是8,所以结果是min(1,8)=1,所以也是相邻存放的,而sizeof(A)的结果是13,所以直接是13+4 = 17.

此时,sizeof(B)的大小是17.

 

(2) 在pack为2的时候,此时min中有一个为2,对于test结构体,它的大小是4+2+8=14,因为在double的时候,min(2,8)=2,所以double类型的变量应该是2的倍数的地址,造成了char类型处空出了一个字节。总体就是14B。而对于B结构体而言,一个int占据4B,然后结构体的对齐参数采用min(2,max(A)),即min(2,8)= 2,由于是int,所以下一个地址是4,自然也是2的倍数,于是还是相邻存放。而A结构体的大小时14,于是B结构体的大小时14+4=18.

(3) 在pack为4的情况下。同样可以得到。此时对于A结构体的大小是4+4+8=16,因为double类型的必须是4的倍数,造成了char变量要占4个地方(实际只占一个,只是说这个地方空出来了3B),所以总体的大小为16.而同样对于B结构体,sizeof的结果是16+4 = 20,因为对于里面的成员要是min(4,8) = 4,而int恰好是4的倍数,所以相邻存放。于是就是16,20.

(4) 在pack为8的情况下(有所变化!!!),此时A结构体的大小是16,分析方法和上面相同,但是对于结构体B而言就有所区别,此时int还是4个字节,但是对于成员test结构体,它的对齐参数是min(8,max(A)) = min(8,sizeof(double) ) = 8也就是对齐参数是8,所以结构体变量要从地址为8开始,此时int就空出来了4B,从而最后求大小的时候应该是8+sizeof(A)= 8+16=24(最终测试结果如此)

(5)在pack为16的情况(以及以后的情况),结果是:A的大小为16B,而B的大小是24B。

 

 

总结:

(1)       对于一个由简单类型组成的结构体,它的大小是由每一个成员变量的地址决定的。我们要按照定义的顺序,分别求出来地址开始的地方。从地址为0开始,每一个变量都采取min(n,sizeof(x))//x表示该变量的类型;来确定起始地址是多少的倍数,然后开始计数,直到填满该数据。最后求出来总的大小。而且在pack>=2的时候最终的大小需要时2的倍数,有时候需要向上取大为2的倍数。而在pack为1的情况则不需要。

(2)       对于含有结构体成员的结构体,方法同上,只是在于对于结构体变量的对齐参数取法需要说明,具体就是min(n,结构体成员的最大元素的大小),就像上面的,结构体B中含有A成员,所以对齐参数就是min(n,sizeof(double))的大小,然后按照这个做法来取地址。


P.S:注意这里是pack而不是package,否则编译器会直接忽略#pragma package(),因为即使发生错误编译器也会直接忽略,而我们还是会默认认为编译器已经当做了字节按照n来处理。(某些博客上面的内容很容易让人误解或者晕倒!)

以上代码结果在Dev C++ , C-Free 5.0,VS 2010上均通过测试。

参考资料:
http://www.cppblog.com/deercoder/archive/2011/03/13/141717.aspx

<think>嗯,用户问的是C语言中的#pragma pack(push)#pragma pack(pop),需要我用中文介绍一下。首先,我得回忆一下这两个预处理指令的作用。它们通常和结构体的内存对齐有关。对吧?当编译器处理结构体时,默认会有对齐规则,比如按照4字节对齐,这样结构体成员之间可能会有填充字节,以优化访问速度。但有时候我们需要改变这种对齐方式,尤其是在处理硬件相关或需要特定内存布局的时候,比如网络协议包或者硬件寄存器映射。这时候#pragma pack就有用了。 那#pragma pack(push)和pop的作用呢?我记得push是用来保存当前的对齐状态,然后可以设置新的对齐方式,而pop则是恢复之前保存的状态。这样可以在需要的地方临时改变对齐,而不会影响其他代码部分的对齐设置。比如在一个头文件中,可能需要改变结构体的对齐方式,但之后用pop恢复,这样其他结构体还是按照原来的对齐方式处理。这样可以避免全局改变对齐带来的潜在问题。 用户可能需要了解这两个指令的基本用法,以及它们的作用场景。也许他们遇到了结构体内存对齐的问题,或者在跨平台开发时遇到了数据布局不一致的情况。也有可能是在学习如何优化内存使用,或者与外部设备进行数据交互时需要精确控制结构体的布局。 需要解释清楚#pragma pack()的作用,它接受一个参数,通常是1、2、4等,来指定对齐的字节数。而push和pop则是用来保存和恢复对齐状态的堆栈操作。例如: #pragma pack(push, 1) struct MyStruct { char a; int b; }; #pragma pack(pop) 这里,push将当前的对齐方式压入堆栈,并设置新的对齐为1字节,即无填充。结构体MyStruct大小就会是1+4=5字节,而正常情况下可能是8字节(假设默认4字节对齐)。之后,pop恢复之前的对齐方式,确保后续的代码不受影响。 需要注意的是,不同的编译器可能有不同的实现,但大多数主流编译器如GCC、Clang、MSVC都支持这种语法。另外,过度使用或错误使用pack可能导致程序性能下降,因为非对齐内存访问在某些架构上较慢,甚至引发错误。因此,只有在必要时才应使用,并且要确保理解其影响。 可能用户还不太清楚为什么需要push和pop,而不仅仅是直接设置pack(1)然后再设回原来的值。这时候需要解释,因为直接设置的话,如果原来的对齐值未知,或者代码被多次包含,可能会导致问题。而push和pop自动保存和恢复,更加安全可靠。 另外,可以举一些实际应用例子,比如网络传输中数据包的封装,或者与硬件寄存器交互时需要的精确内存布局。这种情况下,必须确保结构体没有填充,以符合协议或硬件的要求。 总结来说,需要明确这两个指令的作用、使用场景、注意事项,以及正确使用的示例。同时提醒用户谨慎使用,避免不必要的性能问题。</think>在 C 语言中,`#pragma pack(push)` 和 `#pragma pack(pop)` 是用于控制**结构体(或联合体)内存对齐方式**的预处理指令。它们通常用于需要精确控制内存布局的场景(例如网络协议、硬件交互或跨平台兼容性开发)。 --- ### **基本作用** 1. **`#pragma pack(push, n)`** - 将当前编译器的**默认对齐值**保存到内部堆栈中。 - 设置新的对齐值为 `n` 字节(`n` 通常是 1、2、4、8 等 2 的幂)。 - 结构体成员将按照 `n` 字节对齐,减少或消除填充字节,可能牺牲性能以换取紧凑内存布局。 2. **`#pragma pack(pop)`** - 从内部堆栈中弹出最近保存的对齐值,**恢复之前的对齐设置**。 - 避免对齐方式的修改影响后续代码。 --- ### **典型使用场景** ```c #pragma pack(push, 1) // 保存当前对齐,设置新对齐为 1 字节(无填充) struct Packet { char header; int data; short checksum; }; // 结构体大小 = 1 + 4 + 2 = 7 字节(默认对齐下可能为 12 字节) #pragma pack(pop) // 恢复之前的对齐方式 ``` - **作用**:强制 `struct Packet` 的成员紧密排列,无填充字节。 - **用途**:网络协议包、二进制文件格式、硬件寄存器映射等需要内存布局精确控制的场景。 --- ### **注意事项** 1. **性能影响** 对齐值过小(如 `1`)可能导致非对齐内存访问,在某些架构(如 ARM)上引发性能下降甚至硬件异常。 2. **编译器兼容性** 语法是编译器相关的,但主流编译器(GCC、Clang、MSVC)均支持 `#pragma pack`。 C11 标准提供了 `_Alignas` 和 `alignof` 作为更标准的替代方案。 3. **作用域** `#pragma pack` 的作用域是从声明处开始,直到遇到 `#pragma pack(pop)`,或文件结束。 --- ### **对比直接设置对齐** ```c #pragma pack(1) // 直接设置对齐为 1 struct A { ... }; #pragma pack(4) // 强行修改对齐为 4 struct B { ... }; ``` - ❌ 直接修改对齐值可能导致不可预料的副作用(例如其他代码依赖原对齐方式)。 - ✅ **`push/pop` 更安全**:通过堆栈机制确保对齐方式的局部性。 --- ### **总结** | 指令 | 行为 | 用途 | |-----------------------|-----------------------------------|--------------------------| | `#pragma pack(push, n)` | 保存当前对齐,设置新对齐为 `n` | 临时修改对齐,控制内存布局 | | `#pragma pack(pop)` | 恢复最近保存的对齐方式 | 确保后续代码不受影响 | 合理使用 `push/pop` 可以在需要时精确控制内存布局,同时避免全局对齐设置的副作用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值