最近在看c语言结构体内存优化,发现一个这个一个重要难点,写此文章记录下来。
以下图表是在不同系统中基本类型数据内存大小和默认对齐模数:
以下内存都是从0地址开始。
首先请看下面题目:
typedef struct BIT_T{
char buf[1];
} BIT;
以上结构体内存占用情况怎么样?
答案是1字节
那么如果是以下结构体呢
typedef struct BIT_T2{
char buf[1];
long ret;
// short bit;
} BIT2;
所占用内存是多少呢?
答案是在32位机器为12,在64位机器为16。
有很多小伙伴误以为在64位机器上是12个字节,认为只要4字节对齐就好,其实结构体分配内存不是简单4个字节对齐。
首先要弄懂电脑结构体分配对齐内存原理
- 1.对于结构体的各个成员,第一个成员的偏移量是0,排列在后面的成员其当前偏移量必须是当前成员类型的整数倍
- 2.结构体内所有数据成员各自内存对齐后,结构体本身还要进行一次内存对齐,保证整个结构体占用内存大小是结构体内最大数据成员的最小整数倍
- 3.如程序中有#pragma pack(n)预编译指令,则所有成员对齐以n字节为准(即偏移量是n的整数倍),不再考虑当前类型以及最大结构体内类型
再看上面结构体,在64位系统中 buf确实占用1个byte,占用地址0,但是long类型占用8个字节,但是根据对齐原理第一条,地址2-7都不是8的倍数,所以2-7地址填充空数据,所以ret起始地址为8。总体大小为 8(buf)+8(ret)=16。
那么如果是下面呢
typedef struct BIT_T3{
long ret;
char buf[1];
// short bit;
} BIT3;
答案是在32位机器为8,在64位机器为16。
元素ret大家肯定知道是占用地址0-7,为什么总大小是16字节,而不是9或者12呢。
原因是根据内存对齐原理第二条,整个结构体占用内存大小是结构体内最大数据成员的最小整数倍,当buf在地址8的时候,占用总大小为9,不是8的倍数,所以内存总大小为16。
如果是下面结构体呢?总大小是多少?
typedef struct STD_T{
int human;
char name[4];
char sex[10];
int *ptr;
int goal;
} STD;
答案在32位系统为28 ,在64位系统为是40。
在32位系统中,human 的 类型为int 4个字节,占用0-3地址,name为4个字节,占用4-7字节。sex占用10个字节,占用8-17字节,由ptr指针占用4个字节,并且18-19不是4的倍数,所以18-19为填充空数据,ptr地址由20开始,占用20-23。goal为4个字节占用为24-28。
总体大小为4(human)+4(name)+12(sex)+4(ptr)+4(goal)=28个字节,28刚好是4的倍数。满足内存对齐原理。
在64位系统中,human 的 类型为int 4个字节,占用0-3地址,name为4个字节,占用4-7字节。sex占用10个字节,占用8-17字节,由ptr指针占用8个字节,并且18-23不是4的倍数,所以18-23为填充空数据,ptr地址由24开始,占用8个字节,地址为24-32。goal为4个字节占用为32-36。
由于36不是8的倍数,所以36-40为填充空数据。
4(human)+4(name)+16(sex)+8(ptr)+8(goal)=40个字节
那么以上结构体可以优化吗?是可以的。
我们可以把以上顺序进行优化。
typedef struct STD_T1{
int *ptr;
int human;
int goal;
char name[4];
char sex[10];
} STD1;
结构是在32位系统为28,在64位系统为32,过程请参考上面推导方式。
对于字节对齐,我们可以自定义,请看一下这句话:
64 位 Linux 下可以按 8 字节及以下的任意字节对齐,32 位只能按 4 字节及以下任意字节对齐。
有的程序加上以下这句话,这句话意思是按照某个字节对齐
#pragma pack(n)
每个特定平台上的编译器有自己默认的“对其系数”,程序员可以通过与编译命令,#pragma pack(n),n=1,2,4,8,16来改变这个系数。
如果执行以下程序
#pragma pack(1)
typedef struct STD_T{
int human;
char name[4];
char sex[10];
int *ptr;
int goal;
} STD;
在32位系统占用26个字节,在32位系统占用30个字节,比没有#pragma pack(1)多了几个字节,意味着更省内存,有的小伙伴认为所有头文件都加上这个,这样不是更好吗?
设置结构体的边界对齐为1个字节,也就是所有数据在内存中是连续存储的。但是为什么没有#pragma pack(1)这句话,反而内存增大了.为了让程序跑得跟快,减少CPU读取数据的指令周期,对结构体的存储进行了优化。