关于结构体大小的计算问题(c语言)

一、什么是字节对齐

        在计算机中,我们所有创建的数据存储在内存中。当我们开始定义一个结构体,它会占用一段内存空间。理论我们应该按照各自的数据类型所占用的字节任意的存储,我们也可以从任意起始地址访问任意变量,但在实际内存读取中,访问数据变量时一般是从特定的内存起始地址进行访问, 因为cpu读取并非是逐字节读取的,而是按照2,4,8这样的倍数读取,一般是从偶数地址开始,在不同的平台上对齐方式也是不一样的。32位系统中,CPU一次可以存取4字节的数据;64位系统中,CPU一次可以存取8字节的数据。这就需要各种数据类型按照一定的规则在空间上进行排列,而不是顺序地一个接一个地存放,这就是字节对齐。

二、字节对齐的原因

        需要字节对齐的根本原因在于CPU访问内存数据的效率问题。

        以32位操作系统的计算机为例,CPU通过数据总线访问(读或写)内存数据,周期地从偶地址开始访问32位内存数据,因为内存数据是以字节为单位存放的。

        所以如果一个32位数据从偶地址开始放,那么一个周期就可以读出来,但要是没有存放在4字节整除的起始地址处,而是从奇数位开始存储,那么CPU就需要2个周期的时间对其进行访问,并对两次读取结果按照高低地址进行拼凑才能得到结果,显然访问效率下降了。

        因此,通过合理的内存字节对齐可以提高CPU访存效率。

        一般来说,在Windows平台上默认对齐方式是8字节,Linux系统上默认对齐方式是4字节。

        我们也可以通过预指令#pragma pack(n)来修改对齐方式,n一般可选设为1,2,4,8,16

三、字节对齐的现象

        很多人对字节对齐的概念还是有点抽象,我们举个例子说明一下到底什么是字节对齐

#include<stdio.h>
#pragma pack(4)
struct student {
	char q; 
	char p; 
	long long o; 
	short c;
	int d; 
};
int main()
{
	student stu1{ 2,1,3,2,6};
	int d = sizeof(stu1);
	printf("%d", d);
	return 0;
}

        在这个例子里我设置字节对齐方式为4字节,还有一个自己定义的学生类型的结构体,包含了两个char类型,一个long long,一个short,一个int,按照我们惯有的思维肯定是觉得这个结构体大小应该是1+1+8+2+4=16,我们来看输出结果:

结果是20个字节,与我们的预期不相符,这就是cpu读取方式造成的结果。

下面我们先来查看一下这个结构体在内存中的分布情况:

我们可以得出这个结构体的内存地址应该是从affca4----affcb8,共20个字节

这个结构体的内存开辟是:给第一个char类型开辟了1字节,给第二个char类型开辟了3字节,给longlong开辟了8字节,给chort类型开辟了4字节,给int开辟了4字节。

按照我们课本上学习的关于数据类型所占内存大小来看,出现了两处不匹配的情况,第一是在定义第二个char类型时本该分配一个字节内存空间的时候,操作系统给分配了3个字节,第二个是在定义short类型时,本该分配2个字节大小,实际上分配了4个字节。

这里出现的情况就是因为编译器在编译程序时要对结构体的成员在存储空间上进行字节对齐的缘故。

四、字节对齐规则

(1)三条规则

1、结构体变量的首地址,必须是min{结构体中最大数据类型所占字节数,指定对齐方式}大小的整数倍;

2、结构体每个成员的地址相对于结构体首地址的偏移量,都是min{该数据类型所占字节数,指定对齐方式}大小的整数倍;

3、结构体的总大小,为min{结构体中最大数据类型所占字节数(如果有嵌套结构体也算上),指定对齐方式}的整数倍。

(2)对上述规则的说明:

第1条:编译器在给结构体开辟内存空间时,首先找到结构体成员中最大的基本数据类型,跟对齐方式进行比较,找出相对来说小的那个,然后寻找一个能被这个min值所整除的地址作为结构体的首地址。

第2条:为结构体的一个成员变量开辟空间时,编译器先比较该数据类型和对齐方式哪个的字节数,选取相对小的字节,然后检查预开辟空间的首地址相对于结构体首地址的偏移量是否是相对小的字节的整数倍,如果是,则在该地址存放该成员;如果不是,则在该成员和上一个成员之间填充一定数量的多余字节,使预开辟的首地址相对于结构体的首地址的偏移量达到整数倍的要求,也就是将预开辟空间的首地址后移若干字节。

第3条:结构体实际占用的总空间大小,一定是min{结构体中最大数据类型所占字节数(如果有嵌套结构体也算上),指定对齐方式}的整数倍,比如说我们通过上述规则计算出结构体总大小为18字节,但是我们现在通过比较出来的那个最小的字节数为4,那么这个结构体的总大小为20字节。

五、计算结构体内存大小的步骤

下面我们来介绍一下结构体内存大小的计算步骤:

        还是用我们上面用到的那个实例:

#include<stdio.h>
#pragma pack(4)
struct student {
	char q; 
	char p; 
	long long o; 
	short c;
	int d; 
};
int main()
{
	student stu1{ 2,1,3,2,6};
	int d = sizeof(stu1);
	printf("%d", d);
	return 0;
}

        第一步,从开辟结构体的首地址开始,找要定义的这个结构体的第一个数据类型char开始,比较它和指定对齐方式的哪个字节数小,因为我们设置的指定对齐方式是4字节,结构体里面对大数据类型是longlong,是8字节,所以我们用4字节来作为对齐方式,开辟一个结构体的首地址;

        第二步,开辟第一个数据类型char的地址,通过min{该数据类型所占字节数,指定对齐方式},得到以1字节进行判断,因为他的首地址就是结构体的首地址,所以偏移量是0,是1的整数倍,所以开辟1字节(一般来说开辟第一个数据类型直接按照该数据类型的字节大小进行开辟就行,因为所有数乘以0都是0);此时结构体大小为1字节

        第三步,开辟第二个数据类型char的地址,通过min{该数据类型所占字节数,指定对齐方式},得到以1字节进行判断,预开辟首地址相对于结构体首地址的偏移量是1,是1的整数倍,所以开辟1字节;此时结构体大小为2字节

        第四步,开辟第三个数据类型longlong,通过min{该数据类型所占字节数,指定对齐方式},得到以4字节进行判断,预开辟首地址相对于结构体首地址的偏移量是2,不是4的整数倍,所以将预开辟空间的首地址后移2字节,然后开辟8字节;此时结构体大小为12字节

        第五步,开辟第三个数据类型short,通过min{该数据类型所占字节数,指定对齐方式},得到以2字节进行判断,预开辟首地址相对于结构体首地址的偏移量是12,是4的整数倍,所以开辟2字节;此时结构体大小为14字节    

        第六步,开辟第三个数据类型int,通过min{该数据类型所占字节数,指定对齐方式},得到以4字节进行判断,预开辟首地址相对于结构体首地址的偏移量是14,不是4的整数倍,所以将预开辟空间的首地址后移2字节,然后开辟4字节;此时结构体大小为20字节

        最后,判断此时结构体大小是不是min{结构体中最大数据类型所占字节数(如果有嵌套结构体也算上),指定对齐方式}的整数倍,此时结构体大小为20字节,是4的整数倍,所以该结构体的大小为20字节。

  • 31
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值