文章目录
- 前言
- [1. 结构体的内存对齐(重点)](#1. 结构体的内存对齐(重点))
-
- [1.1 偏移量](#1.1 偏移量)
-
- [1.1.1 offsetof 计算结构体成员的偏移量](#1.1.1 offsetof 计算结构体成员的偏移量)
- [1.2 内存对齐的规则](#1.2 内存对齐的规则)
-
- [1.2.1 计算内存对齐的几个实操例子](#1.2.1 计算内存对齐的几个实操例子)
- [1.3 结构体内存对齐的意义](#1.3 结构体内存对齐的意义)
- [2. 小结](#2. 小结)
前言
在详解结构体(上)这篇文章中我们已经对结构体有了初步的认识。那么在本文中,我们将深入探讨结构体是如何在内存中存放的,以及一些可能你从未听过但实际上且十分常用的语法------位段。那么话不多说,让我们开始本次的探索之旅吧!!!🚢😎😎
1. 结构体的内存对齐(重点)
回想一下数组在内存中是连续存放的,那我们就会提出一个疑问,结构体难道也会是这样的吗?
在解决这个问题之前,我们先插入一个知识点------偏移量
1.1 偏移量
所谓偏移量,就是结构体成员在内存中的首地址相较于整个结构体在内存中初始位置的差值。显然,第一个结构体成员的偏移量一定为0。
如果你还不理解上面的表述,那么请看下面的图解:
1.1.1 offsetof 计算结构体成员的偏移量
C语言其实也提供了函数给我们计算出每个结构体成员相较于起始位置的偏移量。
offsetof函数的其原型为offsetof(type,member),返回值为size_t类型。
这里大家可以先不用管为什么会是这个数字,等到后面就会揭晓答案!
1.2 内存对齐的规则
- 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 对齐数 = 编译器默认的⼀个对齐数与该成员变量大小的较小值。
- VS 中默认的值为 8
- Linux中 gcc 没有默认对齐数,对齐数就是成员自身的大小
- 结构体总大小为最大对齐数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对⻬数)的整数倍。
1.2.1 计算内存对齐的几个实操例子
第一个:
计算下面结构体的大小
c
struct S1
{
char c1;
int i;
char c2;
};
根据规则的第一条: 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处
也就是说变量c1从偏移量为0的地方开始存放。
紧接着是规则的第二条:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。这里我们是在VS的环境下进行测试。我们都知道一个整型变量的大小为4个字节,为VS编译器默认是为8个字节,取两者中的较小值(也就是4)作为对齐数。那此时变量i就得存放在偏移量为4的整数倍的地址处,这里也就是存放在偏移量为4的地址处。(其它变量类似)
接下来是规则的第三条:结构体总的大小为最大对其书的整数倍。在上面的例子中,也就是4为最大,其余都为1。因此,结构体总的大小一定为4的整数倍,如果你计算出来的大小不够4的整数倍那就向上取最接近4的整数倍的数据。(注意本次解释的4仅针对本例,并不是每个结构体都是一样的,具体情况具体分析)
图解如下:
可以看到总共得到了9个字节的大小,但是还不是4的倍数,为此我们应该取到12字节的大小。
第二个例子:
碍于篇幅的限制,我这里就简写出计算过程。
c
struct S2
{
// 成员大小 VS默认的对齐数 对齐数(两者的较小值)
char c1; // 1 8 1
char c2;// 1 8 1
int i; // 4 8 4
};
可以看到最后这个结构体总的大小一定为4的整数倍。
读者们一定要注意的一点是:结构体成员开始存放的位置一定是相应对齐数的整数倍地址处。
第三个例子:
c
struct S3
{
// 成员大小 VS默认的对齐数 对齐数(两者的较小值)
double d; // 8 8 8
char c; // 1 8 1
int i; // 4 8 4
};
例子四:
c
struct S3
{
double d;
char ch;
int i;
};
struct S4
{
char c1;
struct S3 s3;
double d;
};
本例中涉及到了嵌套结构体,得在三条规则的基础上,再多加一条规则:如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对⻬数)的整数倍。
也就是说嵌套结构体的对齐数取决于其自身的最大对齐数,然后依据这个对齐数,对齐到相应的地址处。
图解:
好了,例子已经全部讲完了,希望读者们能够好好的理解结构体内存对齐的四条规则。
1.3 结构体内存对齐的意义
相信很多读者虽然已经掌握了结构体内存对齐的玩法,但是对于为什么要进行内存对齐这个行为感到十分的疑惑。那么在这里,我就跟大家聊聊内存对齐所带给我们的好处。
平台移植
不是所有的硬件都能随意的访问任何地址处的数据。也就是说,某些硬件只能访问特定地址处的某些特定类型的数据,否则就会抛出硬件异常。
性能优化
数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于,为了访问未对⻬的内存,处理器需要
作两次内存访问;⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节,则地
址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数,那么就可以
⽤⼀个内存操作来读或者写值了。否则,我们可能需要执⾏两次内存访问,因为对象可能被分放在两
个8字节内存块中。
举个例子:
总而言之,可以看结构体内存对齐是用空间来换取效率的一种策略。
2. 小结
在本文中主要介绍了结构体的内存对齐,这个是以后大家面试可能会遇到的问题。
学习很难,但坚持一定很酷!!!❤️❤️❤️