<span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-size: 24px;">首先,看下题。</span>
</span>
<span style="font-family: Arial, Helvetica, sans-serif;">#include <iostream></span>
#include<stdio.h>
#include"head.h"
#pragma pack(8)
using namespace std;
struct abcd
{
int a;
char b;
short c;
double d;
};
struct cabd
{
short c;
int a;
char b;
double d;
};
int main()
{
cout<<sizeof(abcd)<<endl;
cout<<sizeof(cabd)<<endl;
}
结果:
16
24
要能清除的分析上面的问题就要搞清楚结构体变量的成员在内存里是如何分布的、成员先后顺序是怎样的、成员之间是连续的还是分散的、还是其他的什么形式?其实这些问题既和软件相关又和硬件相关。所谓软件相关主要是指和具体的编程语言的编译器的特性相关,编译器为了优化CPU访问内存的效率,在生成结构体成员的起始地址时遵循着某种特定的规则,这就是所谓的 结构体成员“对齐”;所谓硬件相关主要是指CPU的“字节序”问题,也就是大于一个字节类型的数据如int类型、short类型等,在内存中的存放顺序,即单个字节与高低地址的对应关系。字节序分为两类:Big-Endian和Little-Endian,有的文章上称之为“大端”和“小端”,他们是这样定义的:
Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。
为了优化CPU访问内存的效率,程序语言的编译器在做变量的存储分配时就进行了分配优化处理,优化规则大致原则是这样:对于n字节的元素(n=2,4,8,...),它的首地址能被n整除,这种原则称为“对齐”,如WORD(2字节)的值应该能被2整除的位置,DWORD(4字节)应该在能被4整除的位置。对于结构体来说,结构体的成员在内存中顺序存放,所占内存地址依次增高,第一个成员处于低 地址处,最后一个成员处于最高地址处,但结构体成员的内存分配不一定是连续的,编译器会对其成员变量依据前面介绍的 “对齐”原则进行处理。对待每个成员类似于对待单个n字节的元素一样,依次为每个元素找一个适合的首地址,使得其符合上述的“对齐”原则。
通常编译器中可以设置一个对齐参数n,但这个n并不是结构体成员实际的对齐参数,VC++6.0中结构体的每个成员实际对齐参数N通常是这样计算得到的
N=min(sizeof(该成员类型),n)(n为VC++6.0中可设置的值)。
成员的内存分配规律是这样的:从结构体的首地址开始向后依次为每个成员寻找第一个满足条件的首地址x,该条件是x % N = 0,并且整个结构的长度必须为各个成员所使用的对齐参数中最大的那个值的最小整数倍,不够就补空字节。
结构体中所有成员的对齐参数N的最大值称为结构体的对齐参数。
gcc编译器中默认的n值为8,可以通过程序进行控制,如
#pragma pack(n) ,n=1,2,4,8,12.其他的参数是无效的。
回过头来看上面的题目,
#pragma pack(8)没有修改默认值,因此,对齐参数仍为8。
struct abcd结构体,
int a,占4字节,N=4,0%4=0,偏移地址为0,地址为[0-3];
char b,占1字节,N=1,4%4=0,偏移地址为4,地址为[4];
short c,占2字节,N=2,6%2=0,偏移地址为6,地址为[6-7];前面补一位
double d,占8字节,N=8,8%8=0,偏移地址为8,地址为[8-15];
整个结构体的N=8,结构体长度为16字节。
struct cabd结构体,
short c,占2字节,N=2,0%2=0,偏移地址为0,地址为[0-1];
int a,占4字节,N=4,4%4=0,偏移地址为4,地址为[4-7];前面补2位
char b,占1字节,N=1,8%1=0,偏移地址为8,地址为[8];
double d,占8字节,N=8,16%8=0,偏移地址为16,地址为[16-23];前面补7位
整个结构体的N=8,结构体长度为24.
关于结构体内存对齐
内存对齐”应该是编译器的“管辖范围”。编译器为程序中的每个“数据单元”安排在适当的位置上。但是C语言的一个特点就是太灵活,太强大,它允许你干预“内存对齐”。如果你想了解更加底层的秘密,“内存对齐”对你就不应该再透明了。
一、内存对齐的原因
大部分的参考资料都是如是说的:
1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
二、对齐规则
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。 对齐步骤:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2颗推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
备注:数组成员按长度按数组类型长度计算,如char t[9],在第1步中数据自身长度按1算,累加结构体时长度为9;第2步中,找最大数据长度时,如果结构体T有复杂类型成员A的,该A成员的长度为该复杂类型成员A的最大成员长度。