程序编译器对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。
n字节对齐就是说变量存放的起始地址的偏移量有两种情况:
第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,
第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。
结构的总大小也有个约束条件,分下面两种情况:
如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;
否则必须为n的倍数。
重要的规则
1,复杂类型中各个成员按照它们被声明的顺序在内存中顺序存储,第一个成员的地址和整个类型的地址相同;
2,每个成员分别对齐,即每个成员按自己的方式对齐,并最小化长规则就是每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数中较小的一个对齐(对齐指的是数据类型在内存中的起始的N倍的跨度);(指复杂类型中成员类型中自己的对齐方式,并不是结构体等整体的对齐方式)
3,结构、联合或者类的数据成员,第一个放在偏移为0的地方;以后每个数据成员的对齐,按照#pragma pack指定的数值和这个数据成员自身长度两个中比较小的那个进行;也就是说,当#pragma pack指定的值等于或者超过所有数据成员长度的时候,这个指定值的大小将不产生任何效果;
4,复杂类型(如结构)整体的对齐是按照结构体中长度最大的数据成员和#pragma pack指定值之间较小N_min的那个值进行;
这样在成员是复杂类型时,可以最小化长度;(指整体的对齐方式)
5,结构整体长度的计算必须取所用过的所有对齐参数的N_min整数倍,不够补空字节;也就是取所用过的所有对齐参数中最大的那个值的整数倍,因为对齐参数都是2的n次方;这样在处理数组时可以保证每一项都边界对齐;
整体复杂结构体的长度:(1)复杂结构体中最长的数据成员长度 与“设定默认长度” 的最小长度N_min(2)复杂类型整体实际长度(按照各自成员对齐规则的计算结果) 取N_min的整倍数,且如果有空余则空余补齐。
最优的存储,但是性能可能不是最优。
注意:对齐(对齐指的是数据类型在内存中距离起始长度M_selft倍的跨度, 其中N为本数据成员的长度M_selft),
如果 struct {char a, float b, int c}
因为: char 1bit, float 2Bit, int 4bit.
则实际的内存占用情况: a:[0] X b:[2][3] c:[4][5][6][7]
即: [a][][b][b][c][c][c][c], 因为 a:1首地址, b=2则需要对齐到长度为2的地址[2][3], c=4需要对齐到长度为4的地址
说明:
语法具体分析:
强调一点: #pragma pack(4) typedef struct { char buf[3]; word a; }kk; #pragma pack()
对齐的原则是min(sizeof(word ),4)=2,因此是2字节对齐,而不是我们认为的4字节对齐。
这里有三点很重要:
1.每个成员分别按自己的方式对齐,并能最小化长度
2.复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度
3.对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐
补充一下,对于数组,比如:char a[3];
这种,它的对齐方式和分别写3个char是一样的.也就是说它还是按1个字节对齐.
如果写: typedef char Array3[3];
Array3这种类型的对齐方式还是按1个字节对齐,而不是按它的长度.
不论类型是什么,对齐的边界一定是1,2,4,8,16,32,64....中的一个.
例子一: #pragma pack(4) class TestB { public: int aa;//第一个成员,放在[0,3]偏移的位置,(结构体内部成员对齐, int = 4 )
char a; //第二个成员,自身长为1,#pragma pack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。(结构体内部成员类型对齐, 1 < 4 取最小1字节对齐) short b; //第三个成员,自身长2,#pragmapack(4),取小值,取2,按2字节对齐,所以放在偏移[6,7]的位置。(结构体内部成员类型对齐, 2 < 4 取最小2字节对齐)
char c;//第四个,自身长为1,放在[8]的位置。(结构体内部成员类型对齐, 1 < 4 取最小1字节对齐) };
[5]地址,因为short b为2长度,则需要跳过。
可见,此类实际占用的内存空间是9个字节。根据规则5,结构整体的对齐是min( sizeof(int ), pack_value ) = 4,所以sizeof( TestB ) = 12;
说明:
整体复杂结构体的长度:
(1)复杂结构体中最长的数据成员长度 与“设定默认长度” 的最小长度N_min
(2)复杂类型整体实际长度 取N_min的整倍数,且如果有空余则空余补齐。
例子二: #pragma pack(2) class TestB { public: int aa;//第一个成员,放在[0,3]偏移的位置, (结构体内部成员类型对齐, int=4 > 2 取最小2字节对齐) char a; //第二个成员,自身长为1,#pragmapack(4),取小值,也就是1,所以这个成员按一字节对齐,放在偏移[4]的位置。(结构体内部成员类型对齐, 1 < 2 取1字节对齐) short b; //第三个成员,自身长2,#pragmapack(4),取2,按2字节对齐,所以放在偏移[6,7]的位置。(结构体内部成员类型对齐,2 = 2 取2字节对齐) char c;//第四个,自身长为1,放在[8]的位置。(结构体内部成员类型对齐, 1 < 2 取最小1字节对齐) };
[5]地址,因为short b为2长度,则需要跳过。
可见,此类实际占用的内存空间是9个字节。根据规则5,
可见结果与例子一相同,各个成员的位置没有改变,但是此时结构整体的对齐是min( sizeof(int ), pack_value ) = 2,所以sizeof( TestB ) = 10;
例子三: #pragma pack(4) class TestC { public: char a;//第一个成员,放在[0]偏移的位置, short b; //第二个成员,自身长2,#pragmapack(4),取2,按2字节对齐,所以放在偏移[2,3]的位置。 char c;//第三个,自身长为1,放在[4]的位置。 };
[1]地址,因为short b为2长度,则需要跳过,直接定位到2的倍数的对齐地址。[2][3]。 整个类的实际内存消耗是5个字节,整体按照min( sizeof( short ), 4 ) = 2对齐,所以结果是sizeof( TestC ) = 6;
例子四: struct Test { char x1; //第一个成员,放在[0]位置,
因为short x2为2长度,则需要跳过[1],直接定位到2的倍数的对齐地址。[2][3]。 short x2;//第二个成员,自身长度为2,按2字节对齐,所以放在偏移[2,3]的位置, float x3;//第三个成员,自身长度为4,按4字节对齐,所以放在偏移[4,7]的位置, char x4;//第四个陈冠,自身长度为1,按1字节对齐,所以放在偏移[8]的位置, }; 所以整个结构体的实际内存消耗是9个字节,但考虑到结构整体的对齐是4个字节(默认的对齐方式),所以整个结构占用的空间是12个字节。
例子五: #pragma pack(8) struct s1 { short a; //第一个,放在[0,1]位置,
因为 b 的长度为4, 需要“对齐地址为4倍的地址”,所以忽略掉了[2][3]
long b; //第二个,自身长度为4,按min(4,8) = 4对齐,所以放在[4,7]位置 }; 所以结构体的实际内存消耗是8个字节,结构体的对齐是min( sizeof( long ),pack_value ) = 4字节,所以整个结构占用的空间是8个字节。 struct s2 { char c; //第一个,放在[0]位置, s1 d; //第二个,根据规则四,对齐是min( 4,pack_value ) = 4字节,所以放在[4,11]位置, long long e;//第三个,自身长度为8字节,所以按8字节对齐,所以放在[16,23]位置, }; 所以实际内存消耗是24自己,整体对齐方式是8字节,所以整个结构占用的空间是24字节。 #pragma pack() 所以: sizeof(s2) = 24, s2的c后面是空了3个字节接着是d。
endl;