这篇我们再来接上次的来讲:
1.结构体的内存对齐:
在讲这个知识点之前我们先来做一个题,猜一下:
#include<stdio.h>
struct S
{
char c1;
int i;
char c2;
};
int main()
{
struct S s = { 0 };
printf("%zd\n", sizeof(s));//%zd是用来打印无符号整型的
return 0;
}
我们可以猜一下这串代码的结果是多少:我们一开始是不会感觉char类型是1个字节,int是4个字节,而char还是一个字节,结果就是6个吧。但是答案却是12.
这是为什么呢?这就是要给大家讲的,关于结构体内存对齐的知识。
1.1对齐规则
首先我们要先掌握结构体的对齐规则:
1.结构体的第一个成员 对齐 和结构体变量的起始位置偏移量为0的地址处。
2.其他成员变量要对齐到某个数字(这里的某个数字就是对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。(这点非常重要)
vs中的默认对齐数是8
linux中gcc没有默认对齐数,对齐数就是成员自身大小。
3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对其数中最大的)的整数倍。
4.如果嵌套结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐的整数倍处,结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
下面呢,我们就对这几点细细讲解:
首先就是第一点的起始位置偏移量,这里我们先给出一张图片,当作内存,一个格子就是一个字节
我们可以看到0所代表的那块空间,第1个字节较起始位置的偏移量就是0.第2个字节较起始位置的偏移量就是1,以此类推,这就是偏移量。那我们来看一下第一个是怎么存进去的。
结构体的第一个成员: 对齐 和结构体变量的起始位置偏移量为0的地址处。第一个是char类型的,就占一个格子,就是0对齐的那个格子。
那第二个怎么存进去呢?来看第二条:
2.其他成员变量要对齐到某个数字(这里的某个数字就是对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数与该成员变量大小的较小值。
vs中的默认对齐数是8
linux中gcc没有默认对齐数,对齐数就是成员自身大小。
我们下来分析一下对齐数是什么概念:
首先我们是用vs进行编译的他的默认对齐数是8,但是第二个成员是int类型的占4个字节,所以这个默认对齐数是4,所以第二个成员要对齐到4的整数倍的地址处。又因为0表示的那块空间已经被第一个数占据,第二个成员占据的内存要从4的整数倍开始占用,就是从4这个数开始进行往后在占用4个。
下面给个图理解一下:
看到了没有就是这样,拿下一个成员要占据的内存,也和这个是一个道理。
我们到这里已经占了 9个字节,那下面再来看一下第三条:
3.结构体总大小为最大对齐数(结构体中每个成员变量都有一个对齐数,所有对其数中最大的)的整数倍。
总大小就是:所有成员中取最大的成员的对齐数的整数倍,但是这里你要把你本身就占据的九个字节包含进去,这三个成员中的最大对齐数就是4,他的整数倍就是4或者8或者12。4,8肯定不行,因为本来占据的字节就不够所以说就是12。
结果就是12了。
那接下来我们可以在对几个题进行练习:
struct S2
{
char c1;
char c2;
int i;
};
int main()
{
struct S2 s = {0};
printf("%d\n", sizeof( S2));
return 0;
}
我们可以根据上面的讲解来做一下这个题:答案是8
现在对结构体内存对齐的知识点了解的就差不多了 。
那接下来我们再来说一下关于结构体内存对齐规则的第四点:
4.如果嵌套结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐的整数倍处,结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
下面还是一个例题进行讲解:
#include<stdio.h>
struct S3
{
double d;
char c;
int i;
};
struct S4
{
char c1;
struct S3 s3;//这个s3就是嵌套在s4里面的结构体,那怎么理解第四个规则呢?就像这样
//s3从哪存放呢?要看s3中的最大对齐数,s3所占的字节大小是16,但是我们是用的vs进行编译的,所以对齐数就是8,但s3是占16个字节的,他要从8的倍数处往后数16个字节才可以。
double d;
//把这些成员都存完,占用了32个字节,但是这个结构体是不是就是占用32个字节呢?还要看这个规则的最后一句话:结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。
//这个成员的所有对齐数最大的就是8,而32就是8的整数倍。
};
int main()
{
struct S4 s = { 0 };
printf("%d\n", sizeof( s));
return 0;
}
这个题会稍微有一些复杂,我在代码中进行讲解。
通过这几个题的练习我们可以知道:结构体成员所占用的总内存大小并不是结构体占用的内存大小。对于有没有嵌套我们分为两类:
没有嵌套的情况:结构体的大小是最大对齐数(结构体中每个成员变量都有一个对齐数,所有对其数中最大的)的整数倍。(这就是最后一步的判断)。
有嵌套的情况:结构体的整数大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍。(这也是最后一步的判断)。
这里还有一点非常重要,就是对齐数是看你是从什么地址处开始存放的,而存放多少字节还是要看你这个成员的大小。虽然说对齐数是要默认对齐数和成员变量大小进行比较,但是该占多少字节还是多少字节,和对齐数没有关系的。关于这一点你可以参考上面的代码。
内存对齐很浪费空间但是为什么还要内存对齐呢?接下来我来讲解一下:
1.2为什么存在内存对齐
这个问题有几个原因第一个就是:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因
那怎样在设计结构体的时候,我们既要满足对齐,又要节省空间,如何做到:
那就是让占用内存少的集中在一起:就想这样
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
我们可以看一下s1和s2他两个的类型都是一样的,但是s1和s2所占的空间的大小就是有区别s2占用的内存就是小。
我们知道vs的默认对齐数就是8,那我们能不能修改一下呢?下面继续讲解:
1.3修改默认对齐数
#pragma这个预处理的指令,可以改变编译器的默认对齐数。
#include <stdio.h>
#pragma pack(1)//设置默认对齐数是1,那结果是什么来看一下:
struct S
{
char c1;
int i;
char c2;
};
#pragma ();//取消设置的对齐数,还原为默认对齐数,但是设置的对齐数对上面的结构体是有效的
int main()
{
//
printf("%d\n", sizeof(struct S));
return 0;
}
就像是这样。 当你感觉结构体在对齐方式不合适的时候,我们可以自己更改默认对齐数。
OK了兄弟们今天的讲解完毕了,这个知识点还剩下一些内容我们明天讲完。