C++中结构体内存对齐方式的原理和方法最强逻辑详述

**

结构体对齐方式:

**

  • 结构体中是存在对齐方式的,因为计算机中在32位机的情况下,是按一个字节一个地址的存储着,但是在读取内存时并不是一个字节一个字节的读取的,因为32位机一个寄存器大概能一次读取4个字节,如果每个数据都是一个字节一个字节的操作那么效率会受到很大的影响,所以计算机根据不同的情况对每个类型的数据读取操作有所不同,比如int就是可以一次读取四个字节刚好放够,short一次读二个字节,double的话一次读8个字节,不过32位机寄存器一次最多读四个字节,所以用的是二个寄存器,虽然可以用多个寄存器一次读入多个字节,但这样开销太大,如果一次使用很多寄存器那么效率会大大降低,所以32位上最长的是8个字节的类型,也就是double这样不会很影响效率,所以我们再来说结构体,首先因为它相当于我们自定义的一种类型,所以它可能会很大,也就是占很多字节,因为它里面存储着很多不同类型的数据,是很多数据的集合,谁也不知道要存多少,所以计算机肯定是不会一次全部读入内存中的,其次它里面是各种不同的类型如果随便默认一个读取的数量,那可能导致把一个数据只读了一点,没有读完如果丢失了,可能导致下次读取错误,而且就算没有丢失,那么拼接也很麻烦,然后如果按照不同类型来分别读取的话效率会很慢,因为结构体中的数据必定是在一个连续空间存储着,如果按不同类型不同字节数来读取,那么一个寄存器可以一次最多读4个字节,我如果先读char类型,也就是1个字节,但我后面还有三个字节可以读进去且不影响效率,我为什么不读呢,不在结构体中分类型是因为地址是随机分配的,并且可能不同地址对应不同的类型,但结构体是连续的所以我要想办法将空间利用起来,让一次尽可能多读且不影响效率,最后因为不同的系统的物理设施不同,有的系统可能将int必需放在偶地址才能读(因为偶地址不用拼接,奇地址可能需要拼接浪费时间),所以要进行字节对齐

    先猜一下这个结构体的大小是多少:
    C++中结构体内存对齐方式的原理和方法最强逻辑详述

  • 所以vs下内存对齐的规则是,从第一个字节地址开始存第一个数据,如果后面要存和它相同的大小的数据不会影响对齐方式,最大对齐数是自己,相当于内存是按自己的类型顺序往下放的,如果比它小那也没事也是顺着往下存,只不过最大对齐数不会改变,这个最大的对齐数就是这个结构体中最大的数据所占的字节数,这个最大对齐数的作用是改变计算机每次读取内存块的大小,也就是在这个结构体中每次读取多少字节,最后整个结构体的字节数一定是这个最大对齐数的倍数,因为如果不是倍数,而每次读取的是这个字节数,到最后有一部分读不到就出现错误了,所以最后要和这个最大对齐数对齐,因为我们是按最大数据的字节数来进行读取的,也就是我们每次读取最大数据那么大的字节数,最后也是按最大数据的字节数来进行整体对齐的,所以最后的整体字节数是最大字节数的倍数,所以一定可以一次读完最大数据,好处是增加了效率,我至少读一次可以读到一个数据,如果读到的不是最大数据,我可以一次读好几个数据,增加效率,不过vs有默认对齐数,当最大数据字节数超过这个数时,是按这个默认字节数进行对齐的,因为如果最大数据字节数太大,会影响效率,vs的默认对齐数是8

  • 然后如果比它大,那就要进行内存对齐,我们要按照有效对齐方式进行对齐,有效对齐方式,就是按照这个数据本身的大小进行对齐,也就是说,前面数据所占字节数必需是这个数据的最小倍数,如果不是倍数,那就把下面的空间空着,从是它的倍数的字节数开始算,也就是说内存对齐可能会浪费一部分空间,这样做的原因就是,保证按最大数据字节数对齐的正确性,因为c/c++中类型所占字节数都是1、2、4、8这几个,只要我们保证自己在对齐时上面空间的字节数是自己字节数的倍数,就可以保证自己可以被完整的读到,因为是8的倍数就一定是4的倍数,是4的倍数同样一定是2的倍数,所以说,只要我们保证上面所有字节和是自己的倍数,那就可以保证最后进行读取的字节数,要么是自己本身,要么是自己的倍数,所以如果是自己本身,那访问自己完全可以,如果是自己的倍数,那么顶多多读几个,不会造成只读了一半,然后因为2是1的倍数,4是2的倍数,而1不是2的倍数,2也不是4的倍数,所以说只要上一数据比下面的数据小,就一般都需要进行内存对齐(并不是每次都需要做)

    所以这个结构体的大小我们可以根据监视窗口来验证:
    C++中结构体内存对齐方式的原理和方法最强逻辑详述
    C++中结构体内存对齐方式的原理和方法最强逻辑详述

  • 然后如果我们在结构体中存储数据时,其中有另一个结构体的类型,那么最大对齐数并不是那个结构体的大小,也不会变成默认的对齐数,而是相当于先在外层结构体中把这本结构体的数据展开按顺序放外层结构体中,但不是真正的展开,因为里面结构体的大小不能改变,如果是展开的话,完全按外部结构体的内存对齐方式对齐,那这个结构体会变小,因为少了最后的整体对齐,所以在进行内存对齐时这个里面的结构体在进行对齐完后,要多加一个整体对齐,也就是自己本身的大小不能变,然后按顺序放在外部结构体中,最后就是外部这个结构体中的数据是接着里面这个结构体存的,所以除了里面这个结构体要多进行一次整体对齐,在进行外部数据和内部数据衔接时,要按普通方式进行一次对齐外,其它没有什么改变,按照普通的对齐方式进行对齐,里面这个结构体进行整体对齐时,是按本结构体中最大数据进行对齐的,并不看外部的,因为外部用最大的对齐时,只是想一次能把最大的读完,而里面如果没有那么大的数据,完全可以按照自身最大的对齐,且不浪费空间,而且不会影响自身结构体的大小,但内部结构体的数据会影响外部的,如果内部有一个大的数据,那么最后在进行整体对齐时,会按照这个最大的数据进行对齐,因为内部这个结构体是按顺序放在外部这个结构体中,所以它要一次把这个最大的数据能读进来,也就是外部不会影响内部的,而内部的会影响外部,因为内部的结构体要按顺序放在外部这个结构体中

可以先想一下这个test结构体的大小:
C++中结构体内存对齐方式的原理和方法最强逻辑详述

  • 我们可以分析一下它所占字节的情况,首先a是char类型,占一个字节,但是它后面是int所以要进行内存对齐,所以char提升为4个字节,然后前两个类型共占8个字节,然后下一个有结构体类型,所以先将这个结构体展开,double占8个字节,上面两个数据也占8个字节所以不需要对齐,然后是一个int占4个字节,但它是里面这个结构体最后一个字节,所以得按照里面这个结构体最大的数据进行一次整体的内存对齐,所以它提升为8个字节,所以现在一共是24个字节,最后一个是short占两个字节,但现在最大的数据是double8个字节,所以它是最后一个字节,整体进行对齐后,必需把24+2=26个字节对齐为8的倍数,所以就是32个字节

然后我们来验证一下结果:
C++中结构体内存对齐方式的原理和方法最强逻辑详述

  • 最后就是有一个宏可以改变默认的对齐数,就是#pragma pack(num),用户可以根据这个宏以自己的方式控制内存来达到最优的效率

这个是将默认对齐设为4:
C++中结构体内存对齐方式的原理和方法最强逻辑详述
然后上个结构体大小就变为了24:

C++中结构体内存对齐方式的原理和方法最强逻辑详述

  • 还有一种理解方式就是,因为不同类型的数据存储在不同的类型的地址中,所以每个数据在存储时,要按照用自身地址和自身字节大小取模=0来确定开始存放的位置,然后计算机根据第一个数据选择一个合适的地址开始存,不过我比较推荐看我前面总结的逻辑