【C语言】——结构体进阶:结构体的内存对齐(超详细)

前言:

        上一篇已经讲了结构体的基本用法。相信各位小伙伴以经学会怎么使用。但是还有一个问题没有弄明白——结构体到底多大,占内存空间多不多,以经系统到底怎么访问结构体内的数据的。

        接下来,详细分析一下结构体的内在……

目录

1.修炼内功,掌握结构体的内存对齐

1.1结构体对齐规则四步走:

2.为什么要会存在内存对齐

2.1怎么修改默认对齐数

3.结构体传参

4.叠甲


1.修炼内功,掌握结构体的内存对齐

        上篇已经讲述了结构体的基本使用,现在来深入探究下一个问题:计算结构体的大小。这个问题也会经常出现在各个大厂的笔试题目中,即——结构体对齐。

        话不多说。直接在下面的代码中感受下:

int main()
{
	//练习1
	struct S1
	{
		char c1;
		int i;
		char c2;
	};
	printf("%d\n", sizeof(struct S1));
	//练习2
	struct S2
	{
		char c1;
		char c2;
		int i;
	};
	printf("%d\n", sizeof(struct S2));
    return 0;
}

        (留给空间给盆友么思考!!!先思考,再看下面的)        

        盆友们,对于练习1和练习2的的答案是多少呢?

        相信刚接触的铁子们肯定觉得s1和s2的长度都是6吧(32位系统中)。这很简单呀,s1和s2里面都是char char int  三个变量类型,为 1 1 4。所以加起来是6咯。这有啥难的。

        其实,非也。咱们调试起来瞧瞧看。

        是 12 和 8……

        为啥会这样呢?

        12 和 8 这个答案是不是说明了s1和s2里面的数据居然不是挨着存的(如果是挨着的,就是6),显然之间存了空白的东西。

        既然结构体里面存了空白的位置,那么不妨试试看,结构体内两个变量之前到底相隔多少空白位。

        这里用到offsetof函数

        这个函数是用来计算结构体成员相对于结构体起始位置的偏移量。(不理解没关系,下面会有图解。)

        函数的用法可以参考:(30条消息) C语言中 offsetof 的使用_Npgw的博客-CSDN博客_offsetof头文件

        (随便找的,我真贴心!!!)

        话不多说,请看代码  :

int main()
{
	//练习1
	struct S1
	{
		char c1;
		int i;
		char c2;
	};
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", offsetof(struct S1,c1));
	printf("%d\n", offsetof(struct S1, i));
	printf("%d\n", offsetof(struct S1, c2));
	printf("\n");
	//练习2
	struct S2
	{
		char c1;
		char c2;
		int i;
	};
	printf("%d\n", sizeof(struct S2));
	printf("%d\n", offsetof(struct S2, c1));
	printf("%d\n", offsetof(struct S2, c2));
	printf("%d\n", offsetof(struct S2, i));
    return 0;
}

      答案:

 对于s1:

        c1相对与起始位置偏移量是0个字节

        i相对与起始位置的偏移量是4个字节

        c2相对与起始位置的偏移量是8字节

对于s2:

        c1相对与起始位置的偏移量是0个字节

        c2相对与起始位置的偏移量是1个字节

         i相对与起始位置的偏移量是4个字节

图示:(认真看图!!!营养全在图里面)

S1

S2

         那么关于s1和s2怎么存储的,我们就弄清楚了,那么这样存的依据是什么,按照怎样的规则来存结构体呢?

    接着往下看……

1.1结构体对齐规则四步走:

        1.第一个结构体成员在结构体变量的偏移量为0的地址处

        2.其他成员变量要对齐某个数字(对齐数)的整数倍的地址处

注意:

        对齐数是编译器默认的一个对齐数和改结构体成员变量大小的最小值。

        比如,VS的默认对齐数是8个字节 加入结构体中又int ,那么int就要存到偏移量为4的整数倍的地址处。但是注意的是,Linux系统下不设置对齐数,对齐数字就是结构体成员自身的大小。

        3.结构体总大小为最大对齐数(可能每个成员都有自己的对齐数,找个最大的)的整数倍。

        4.特殊情况:

        如果结构体嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍地址处,结构体整体大小就是所有最大对齐数(包含嵌套结构体)的整数倍数。

为了方便理解,再举个例子:

看代码:

//练习3
int main()
{
	struct S3
	{
		double d;
		char c;
		int i;
	};
	printf("%d\n", sizeof(struct S3));
	//练习4-结构体嵌套问题
	struct S4
	{
		char c1;
		struct S3 s3;
		double d;
	};
	printf("%d\n", sizeof(struct S4));

	return 0;
}

直接用上述规则:

看图(!!!):

s3:

s4:

 详细分析:

        对于第一步,用规则1,直接存。

        第二步,struct s3 s3  里面的最大对数是8,所以存偏移量为8的整数被的的地址处,存16个字节。

        第三步,d的存法如同第二步,也是在8的整数倍开始存(偏移量24地址处)

        第四步,看看结构体整体大小是不是最大对齐数的整数倍,对于练习4刚好是的(32字节),所以不需要补空白位置,假如s4存完之后发现只有30个字节,那么需要在30后面补充两个空白位置,到32(这样才是8的倍数)。

2.为什么要会存在内存对齐

        其实并没有一个官方的说法,但是从大部分的资料上来看,存在内存对齐是出于以下两个考量。

1.平台原因:

        不是所有的硬件平台都能够访问任意地址上的任意数据,某些平台只能在某些地址取出某种特定类型的数据,否则会出现硬件异常。

2.性能原因:

        数据结构(尤其是栈区)应该尽可能的在自然边界上对齐。(感觉咯不懂是吧,没关系,看图)。原因在于,为了访问未对齐的数据,处理器需要两次访问,二对齐的数据只需要一次访问。

        计算机访问数据要么是按照4个字节来访问,要么是按照八个字节来访问,假设某计算机按照四个字节访问数据

        因此,结构体的内存对齐是用空间来换取时间的策略,虽然所占空间大了,但是访问用的时间却短了很多。

        那么怎样设计结构体既能够满足对齐的要求,又满足节省空间呢?

        看看前面的 struct s1 和struct s2 ,明明存的东西一样,但是后者的内存小。

        所以,对于结构体内部成员,相同变量类型的尽量相邻,这样设计的结构体占的空间会相对小一点。

2.1怎么修改默认对齐数

        当然对齐数字,也是能过够修改的

#pragma pack(8)//设置默认对齐数为8
struct S1
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
 char c1;
 int i;
 char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认

3.结构体传参

        结构体传参的两种方式:

        看代码:

struct S
{
 int data[1000];
 int num;
};
struct S s = {{1,2,3,4}, 1000};
//结构体传参
void print1(struct S s)
{
 printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
 printf("%d\n", ps->num);
}
int main()
{
 print1(s);  //传结构体
 print2(&s); //传地址
 return 0;
}

        上面print1和print2函数,哪个更好一点?

        是print2,为什么呢?

        因为函数在传参的时候,参数需要压栈的,会有对时间和空间上的系统开销,如果传递的一个结构体对象的时候,结构体太大,那么参数压栈的系统开销比较大,会导致性能的下降,所以函数传参,尽可能选择传址调用。

4.叠甲

        所有内容都是菜鸟的学习过程记录

        如果有错误的地方,读者轻点喷。

        觉得还行的话,点赞,评论,关注(求求家人们!!!)

        如果有疑问,请在评论区发言,看到的,都会回,爱你们!!!

  • 23
    点赞
  • 50
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
C语言中,结构体内存对齐是一种优化手段,用于提高内存访问效率和减少内存浪费。结构体内存对齐规则是根据结构体成员的类型和顺序,以及编译器的对齐方式来确定的。 根据引用中的例子,我们可以看到结构体s1和s2的成员顺序相同,但是它们的内存对齐结果却不同。结构体s1的大小为8字节,结构体s2的大小为12字节。这是因为编译器在对齐结构体时,会根据最大成员的大小来确定对齐方式。在结构体s1中,最大成员是int类型的c,大小为4字节,所以结构体s1的对齐方式是4字节对齐。而在结构体s2中,最大成员是char类型的b,大小为1字节,所以结构体s2的对齐方式是1字节对齐。 另外,根据引用的例子,我们可以看到结构体s2中嵌套了结构体s1。通过使用offsetof函数,我们可以得到结构体s2中成员a和成员c的偏移量分别为0和4字节,这表明结构体s2中的成员是按照其在结构体中的声明顺序进行排列的。同时,结构体s2的大小为12字节,这是因为结构体s2的对齐方式是最大成员char类型b的大小1字节。 总结起来,C语言结构体内存对齐是根据结构体成员的类型和顺序,以及编译器的对齐方式来确定的。这种对齐可以提高内存访问效率和减少内存浪费。不同的结构体可能有不同的对齐方式和大小,这取决于结构体中最大成员的大小和结构体中的成员顺序。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [【C语言系列】-结构体中的内存对齐](https://blog.csdn.net/m0_64332179/article/details/122682708)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [【C语言】——结构体进阶结构体内存对齐详细)](https://blog.csdn.net/luoheng1114/article/details/127106154)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

待己以诚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值