一.常见的类型长度
#include <iostream>
int main()
{
int a=1;
char b='b';
double c = 2;
int d[]={1,2,3,4};
printf("%d\n",sizeof(a));
printf("%d\n",sizeof(b));
printf("%d\n",sizeof(c));
printf("%d\n",sizeof(d));
}
执行结果:
分析:根据结果,我们知道,int占4个字节,char 占1个,double 占8个,数组的占用跟类型和长度有关
二.结构体变量内存对齐
我们先看一个小例子
#include <iostream>
typedef struct data1
{
char a;
int b;
char c;
};
typedef struct data2
{
char a;
char c;
int b;
};
int main()
{
data1 a1;
data2 a2;
printf("%d\n",sizeof(a1));
printf("%d\n",sizeof(a2));
}
运行结果:
分析:这里很奇怪,我们data1和data2都装的同样的数据只是顺序不一样最后占用的内存大小也不一样
这里就涉及到,关键的部分:内存对齐。
内存对齐规则:
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。gcc中默认#pragma pack(4),可以通过预编译命令#pragma pack(n),n = 1,2,4,8,16来改变这一系数。
有效对其值:是给定值#pragma pack(n)和结构体中最长数据类型长度中较小的那个。有效对齐值也叫对齐单位。
了解了上面的概念后,我们现在可以来看看内存对齐需要遵循的规则:
(1) 结构体第一个成员的偏移量(offset)为0,以后每个成员相对于结构体首地址的 offset 都是该成员大小与有效对齐值中较小那个的整数倍,如有需要编译器会在成员之间加上填充字节。(如果当前成员小于pack, 并且加上下一个成员的总字节数也小于pack 就会直接填充下一个成员,否则填充空白字节)
//内存对齐伪码:
size1=当前成员大小
if size1<pack then
while(有下一个成员&&size1+下一个成员大小<=pack)
填充下一个成员
size1+=下一个成员大小
if(有下一个成员&&size1+下一个成员大小>pack)
填充空白字节
else if(没有下一个成员 && size1%pack!=0) then
填充空白字节
(2) 结构体的总大小为 有效对齐值 的整数倍,如有需要编译器会在最末一个成员之后加上填充字节。
简单来说每个成员是pack的整数倍,每个结构体是pack的整数倍。pack通常是4,可调。
调整pack之后的结果:
代码:
#include <iostream>
#pragma pack(2)
typedef struct data1
{
char a;
int b;
char c;
};
typedef struct data2
{
char a;
char c;
int b;
};
int main()
{
data1 a1;
data2 a2;
printf("%d\n",sizeof(a1));
printf("%d\n",sizeof(a2));
}
运行结果:
分析:
data1中 a本身1字节,pack为2,需要补足,a+b超过了pack,我们进行空白对齐,最后占用2字节
data1中 b本身4字节,pack为2,不需要补足,最后占用4字节
data1中 c本身1字节,pack为2,需要补足,c之后没有下一个成员,直接空白对齐,占用2字节
总计:8字节
data1中 a本身1字节,pack为2,需要补足,a+c没超过pack,我们进行下一个成员填充,最后占用2字节(a+c)
data1中 b本身4字节,pack为2,不需要补足,最后占用4字节
总计:6字节
有数组的情况:
代码;
#include <iostream>
#pragma pack(4)
typedef struct data1
{
char a;
int b;
char c;
char d[3]={1,2,3};
};
typedef struct data2
{
char a;
char c;
int b;
char d[3]={1,2,3};
};
int main()
{
data1 a1;
data2 a2;
printf("%d\n",sizeof(a1));
printf("%d\n",sizeof(a2));
}
结果:
分析:这里data1和data2的大小一样让我们很奇怪。(pack=4)
原因就在c和d的填充。
data1:填充c的时候,c<pack,并且下一个成员的大小为12已经超过了我们所说的,需要填充空白字符,但是结果确是填充了d中的元素。由此分析,如果下一个元素是数组的时候我们需要对其拆分成单个元素进行判断。
我们的对齐伪码变成这样:
size1=当前成员大小
if size1<pack then
if(有下一个成员)
拆分成单个元素进行内存对齐
while(有下一个成员&&size1+下一个成员大小<=pack)
填充下一个成员
size1+=下一个成员大小
if(有下一个成员&&size1+下一个成员大小>pack)
填充空白字节
else if(没有下一个成员 && size1%pack!=0) then
填充空白字节
三.内存对齐的原因
处理器只能从地址为4或者8的倍数的内存开始读取数据(也叫做读行)
如果我们没有使用内存对齐。我们想要获取完整的数据就要读取更多的次数,效率更低。
可以理解成用内存换时间
我们能不能将pack设置成1这样不是又省空间又省时间吗?
不行,pack只是为了兼容cpu读取内存的方式,cpu不会根据你的pack去调整。如果设置成1,cpu还是读行,这样还是要读取更多的次数.