#pragma pack
目录
一、n字节的对齐方式
二、#pragma pack(n) 对齐用法详解
VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
VC中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;
否则必须为n的倍数。下面举例说明其用法。
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态
以上结构体的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用4个字节。接着开始为m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)
什么是对齐,以及为什么要对齐: 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就需要各类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出,而如果存放在奇地址开始的地方,就可能会需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该int数据。显然在读取效率上下降很多。这也是空间和时间的博弈。对齐的实现 通常,我们写程序的时候,不需要考虑对齐问题。编译器会替我们选择时候目标平台的对齐策略。当然,我们也可以通知给编译器传递预编译指令而改变对指定数据的对齐方法。但是,正因为我们一般不需要关心这个问题,所以因为编辑器对数据存放做了对齐,而我们不了解的话,常常会对一些问题感到迷惑。最常见的就是struct数据结构的sizeof结果,出乎意料。为此,我们需要对对齐算法所了解。
作用:指定结构体、联合以及类成员的packing alignment;
语法:#pragma pack( [show] | [push | pop] [, identifier], n )
说明:1,pack提供数据声明级别的控制,对定义不起作用;2,调用pack时不指定参数,n将被设成默认值;3,一旦改变数据类型的alignment,直接效果就是占用memory的减少,但是performance会下降;
语法具体分析:1,show:可选参数;显示当前packing aligment的字节数,以warning message的形式被显示;2,push:可选参数;将当前指定的packing alignment数值进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数值压栈;3,pop:可选参数;从internal compiler stack中删除最顶端的record;如果没有指定n,则当前栈顶record即为新的packing alignment数值;如果指定了n,则n将成为新的packing aligment数值;如果指定了identifier,则internal compiler stack中的record都将被pop直到identifier被找到,然后pop出identitier,同时设置packing alignment数值为当前栈顶的record;如果指定的identifier并不存在于internal compiler stack,则pop操作被忽略;4,identifier:可选参数;当同push一起使用时,赋予当前被压入栈中的record一个名称;当同pop一起使用时,从internal compiler stack中pop出所有的record直到identifier被pop出,如果identifier没有被找到,则忽略pop操作;5,n:可选参数;指定packing的数值,以字节为单位;缺省数值是8,合法的数值分别是1、2、4、8、16。
重要规则:1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长度;规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐;3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;4,复杂类型(如结构)整体的对齐<注意是“整体”>是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小的那个值进行;这样在成员是复杂类型时,可以最小化长度;5,结构整体长度的计算必须取所用过的所有对齐参数的整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;
对齐的算法: 由于各个平台和编译器的不同,现以本人使用的gcc version 3.2.2编译器(32位x86平台)为例子,来讨论编译器对struct数据结构中的各成员如何进行对齐的。
在相同的对齐方式下,结构体内部数据定义的顺序不同,结构体整体占据内存空间也不同,如下: 设结构体如下定义: struct A { int a; char b; short c; }; 结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。所以A用到的空间应该是7字节。但是因为编译器要对数据成员在空间上进行对齐。所以使用sizeof(strcut A)值为8。 现在把该结构体调整成员变量的顺序。 struct B { char b; int a; short c; }; 这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12。 下面我们使用预编译指令#pragma pack (value)来告诉编译器,使用我们指定的对齐值来取代缺省的。 #pragma pack (2) /*指定按2字节对齐,等价于#pragma pack(push,2)*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定对齐,恢复缺省对齐,等价于#pragma pack(pop)*/ sizeof(struct C)值是8。 修改对齐值为1:#pragma pack (1) /*指定按1字节对齐*/ struct D { char b; int a; short c; }; #pragma pack () /*取消指定对齐,恢复缺省对齐*/ sizeof(struct D)值为7。 对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
这里面有四个概念值: 1.数据类型自身的对齐值:就是上面交代的基本数据类型的自身对齐值。 2.指定对齐值:#pragma pack (value)时的指定对齐值value。 3.结构体或者类的自身对齐值:其数据成员中自身对齐值最大的那个值。 4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。 有了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。例子分析: 分析例子B; struct B { char b; int a; short c; }; 假设B从地址空间0x0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。
第一个成员变量b的自身对齐值是1,比指定或者默认指定对齐值4小,所以其有效对齐值为1,所以其存放地址0x0000符合0x0000%1=0.
第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4,所以只能存放在起始地址为0x0004到0x0007这四个连续的字节空间中,符合0x0004%4=0, 且紧靠第一个变量。
第三个变量c,自身对齐值为2,所以有效对齐值也是2,可以存放在0x0008到0x0009这两个字节空间中,符合0x0008%2=0。所以从0x0000到0x0009存放的都是B内容。
再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求,0x0009到0x0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0x0000到0x000B共有12个字节,sizeof(struct B)=12; 同理,分析上面例子C: #pragma pack (2) /*指定按2字节对齐*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定对齐,恢复缺省对齐*/ 第一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0x0000开始,那么b存放在0x0000,符合0x0000%1=0;
第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0x0002、0x0003、0x0004、0x0005四个连续字节中,符合0x0002%2=0。
第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放在0x0006、0x0007中,符合0x0006%2=0。所以从0x0000到0x00007共八字节存放的是C的变量。
又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C只占用0x0000到0x0007的八个字节。所以sizeof(struct C)=8.
分享到:
相关推荐
本文将深入探讨结构体对齐的概念、`#pragma pack` 的使用方法以及其对程序性能的影响。 ### 结构体对齐的基本概念 结构体对齐是指在创建结构体时,编译器会按照一定的规则来安排各个成员变量在内存中的位置,以...
#### 三、常见用法 下面是一些常见的 `#pragma` 指令及它们的功能说明: 1. **alloc_text**:用于指定代码应该被放置到哪个文本段。 2. **comment**:添加编译器注释。 3. **init_seg**:控制初始化段的位置和顺序...
`#pragma`是C语言中的一种预处理器指令,用于向编译器提供特定的命令或信息,以控制编译过程。在嵌入式系统开发,尤其是针对...希望这些信息能解答大家关于`#pragma`用法的疑惑,并帮助你更好地理解和使用这些功能。
七、 #pragma的用法 7 (1) message 参数。 8 (2) 另一个使用得比较多的pragma参数是code_seg。格式如: 8 (3) #pragma once (比较常用) 8 (4) #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译...
通过`#pragma pack`,可以控制这种对齐方式,从而优化结构体的内存使用。 - `#pragma pack(1)`:设置所有数据成员按字节对齐,即不保留任何额外的空隙。 - `#pragma pack(push, n)`:保存当前的对齐设置,并将...
在编写通用的代码时,我们经常使用#pragma pack(1)来使编译器以单字节对齐。但是,这个方法并不能完全解决问题,因为枚举类型的大小仍然是由编译器决定的。 例如,在以下程序中,我们使用#pragma pack(1)来使编译器...
使用方法是 #pragma message("消息文本")。编译器会在编译输出窗口显示括号内的消息文本。 例如,如果你想知道是否定义了某个宏_X86,可以在代码中使用以下方式: ```c #ifdef _X86 #pragma message("_X86 macro ...
综上所述,文档中主要讨论了C语言中内存对齐的概念、结构体大小的计算方法、#pragma pack的应用、模板函数的定义、链接错误的处理,以及编译器和链接器的一些相关选项。这些知识点对于深入了解C语言编程,特别是在...
本文将深入探讨#pragma指令的用法及其如何帮助程序员更有效地管理和优化代码。 1. **#pragma message参数**:这是一个非常实用的功能,它可以在编译过程中向用户输出一条信息。这对于跟踪编译过程和调试特别有用,...
2. **使用`#pragma pack`指令**:这是改变结构体中成员对齐方式最常用的方法之一。例如,在代码示例中通过`#pragma pack(2)`将结构体成员按2字节对齐,而`#pragma pack()`则恢复为默认的对齐方式。 3. **使用GCC...
例如,在Win32平台下的微软C编译器中,我们可以使用#pragma pack(1)、#pragma pack(2)、#pragma pack(4)等指令来指定不同的对齐单位。 通过实践和分析,我们可以看到,结构体的总大小和成员的偏移量都是根据对齐...
- 使用`alignas`关键字或`#pragma pack`指定特定的对齐要求。 - 在设计数据结构时,考虑到对齐的影响,避免不必要的内存浪费。 - 利用编译器提供的内建函数,如`__alignof__`来获取数据类型的对齐值。 理解并...
C++ 中 __declspec 的用法 __declspec 是 C++ 语言中的一种扩展修饰符,用于控制变量或函数的存储类型、对齐方式、数据段等。它的语法格式为:__declspec ( extended-decl-modifier-seq ),其中 extended-decl-...
在描述中提到的`#pragma pack`指令是C/C++预处理器提供的一种方法,用于控制内存对齐策略。`#pragma pack(n)`告诉编译器按照n字节对齐,这里的n可以是一个正整数。当`#pragma pack`没有参数或者参数大于结构体中最大...
C 结构体对齐详解 ...在编写代码时,我们可以使用 #pragma pack 编译参数来指定对齐模数,以满足不同的需求。例如,#pragma pack(2) struct my_struct { char a; long double b; }; 在这个例子中,对齐模数是 2。
SDSoC(Software Defined System on Chip)优化指南主要涉及利用SDx(Software Defined Environment)工具集中的Pragmas(编译指令)来优化系统级设计的性能和资源使用。这些优化指导原则帮助开发者在Xilinx平台上的...