C语言热门考点结构体与内存对齐详解

2022-07-21,,,,

目录
  • 一、引例
    • 1.结构体的第一个成员永远放在结构体起始位置偏移量为0的位置
    • 2.从第二个成员开始,总是放在偏移量为一个对齐数的整数处,对齐数=编译器默认的对齐数和变量自身大小的较小值
    • 3.结构体的总大小必须是各个成员的对齐数中最大的那个对齐数的整数倍
  • 二、小试牛刀
    • 三、嵌套结构体的特殊情况
      • 四、关于为什么存在内存对齐
        • 1.平台原因(移植原因):
        • 2.性能原因:
      • 总结

        一、引例

        到底什么是结构体内存对齐,我们用一段代码来介绍一下

        struct s1
        {
        	char c1;//1字节
        	int a;//4字节
        	char c2;//1字节
        };
        int main()
        {
        	printf("%d\n", sizeof(struct s1));
        	//这里打印12
        }
        

        先来解释s1,结构体s1中有2个char类型,1个int类型。那按道理应该是占2*1+4=6个字节啊,为什么打印的是12呢?到这里,我们必须要来了解一下结构体内存对齐的规则:

        1.结构体的第一个成员永远放在结构体起始位置偏移量为0的位置

        对于偏移量你可以这样理解:数组下标为0的相对它自己偏移量为0,下标为1的相对下标为0的偏移量为1…
        举例说明:

        s1第一个成员是c1,它会被放在结构体起始位置偏移量为0的位置,如下图红色部分

        2.从第二个成员开始,总是放在偏移量为一个对齐数的整数处,对齐数=编译器默认的对齐数和变量自身大小的较小值

        对齐数=min(编译器默认的对齐数,变量自身大小)
        linux-没有对齐数,vs下对齐数默认为8

        我们仍以s1这个结构体进行举例,结构体第二个成员是int类型的a,占4个字节,笔者vs环境下默认对齐数是8,取两者较小值是4,那a应该放到偏移量为4的倍数上

        放到4的倍数上也就说可以放在偏移量为4这里,偏移量为1,2,3的这3个空间就白白被浪费了。而a是int型占4个字节,所以会一直占用到偏移量为7的位置。

        接下来是结构体的第三个成员,char类型的c2,c2占1个字节,vs环境下默认对齐数是8,取较小值为1,也就是说只要是1的倍数的偏移量都可以放,我们紧接着放在a后面,也就是偏移量8的位置

        那到这里结构体3个成员都用完了啊,只有8个啊,为什么打印是12呢?这里就要涉及结构体内存对齐的第3个规则

        3.结构体的总大小必须是各个成员的对齐数中最大的那个对齐数的整数倍

        我们由前面讲解知道结构体三个成员c1,a,c2对齐数分别为1,4,1这三个中最大对齐数是4,总大小要为4的整数倍,那这时候肯定有小伙伴会问:我们现在不是对齐到8了嘛,8不是4的倍数吗?注意!这里说的是空间总大小,而8是所谓的偏移量,偏移量是从0开始算的,到8已经有9个空间了,所以我们这里空间要到12,也就是偏移量到11

        (后面加上的三个空间用不到,但是由于规定还是算在结构体总空间内)

        二、小试牛刀

        我们再来看一道类似的题目

        代码如下(示例):

        struct s2
        {
        	char c1;//1字节
        	char c2;//1字节
        	int a;//4字节
        };
        int main()
        {
        	printf("%d\n", sizeof(struct s2));
        	//这里打印8
        }
        

        首先第一个结构体成员是char类型的c1,由规则1,它会直接被放在偏移量为0的位置
        (图示灰色部分)

        第二个成员是char类型的c2,占1字节,vs下默认对齐数是8,取较小值是1,只要放在偏移量为1的倍数上即可(任意位置),紧跟着0,放在偏移量为1处(图示红色部分)

        最后一个成员int类型的a,占4个字节,vs环境下默认对齐数是8,取较小者4,放在偏移量为4的整数倍处,也就是4这里,然后由于int占4个字节所以一直占用到偏移量7处

        再来看看规则3,结构体的总大小必须是各个成员的对齐数中最大的那个对齐数的整数倍,也就是4的倍数,我们现在正好是占8个空间,8正好是4的倍数,所以就不用再往下浪费空间了,打印出8

        三、嵌套结构体的特殊情况

        代码如下(示例):

        struct s3
        {
        	double d;//double占8字节,默认对齐数8,取较小值,对齐数8
        	char c;//对齐数1
        	int i;//对齐数4
        };
        struct s4
        {
        	char c1;
        	struct s3 s3;
        	double d;
        };
        int main()
        {
        	printf("%d\n", sizeof(struct s4));
        }
        

        关于结构体s3我们可以采用和前面s1、s2一样的方法计算出来是占16个字节空间,我们这里重点讨论s4,对s3有兴趣的小伙伴可自行求解。

        s4中的第一个成员c1,按规则1直接放在偏移量为0处,第二个成员s3怎么办呢?这里涉及结构体内存对齐的第四个规则:

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

        s3这个结构体三个成员最大对齐数是8,也就是要对齐到偏移量为8的倍数处,然后s3是占16个字节,所以一直占到偏移量23处(s3结构体对齐数是本身s3结构体三个成员中最大对齐数)

        ps:在vs环境中,嵌套结构体的最大对齐数超过8,仍然用8做最大对齐数(比默认对齐数大了,取较小值就取默认对齐数了)

        s4最后一个成员double类型的d占8字节,默认对齐数8,对齐数取8,然后放在偏移量为对齐数的整数倍处,正好往下放在24处,本身占8字节所以占到31

        偏移量0-31共占32字节,s4中的成员c1,s3,d对齐数分别为1,8,8所以最大对齐数是8,32恰是8的倍数,所以这里不用再浪费空间来满足 “结构体的总大小必须是各个成员的对齐数中最大的那个对齐数的整数倍”这个规则,结构体总大小就是32

        四、关于为什么存在内存对齐

        1.平台原因(移植原因):

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

        2.性能原因:

        数据结构(尤其是栈),应尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅仅需要1次
        总体来说:结构体的内存对齐是用空间换时间

        总结

        本文介绍了结构体内存对齐的四大规则,并举例说明了如何进行方法的操作,对于其中特殊的嵌套结构体内存对齐也进行了相应讲解,希望读者在学习完本文后能对结构体内存对齐有一个系统的认识。祝读者学业有成!

        《C语言热门考点结构体与内存对齐详解.doc》

        下载本文的Word格式文档,以方便收藏与打印。