C语言或者C++内存粒度对齐是我们经常提到的问题,而对内存的有效合理的利用,必然会使我们写出来的代码更加高效。这几天写代码过程中出现了一个bug,就是由于内存粒度原因导致,所以总结一下。
首先我们来看一下理论知识(我个人理解的):
结构体中数据成员内存对齐的原则:按照一个结构体中的成员中最大字节数整数倍对齐,比如在x86下,出现int(4字节) char(1字节) double(8字节),则肯定是按照最大的8字节整数倍对齐,8字节就相当于一个基准一样,如果遇到的字节数可以在8字节内存放,那就ok,否则需要重新申请8字节来存放,至于说是8的几倍,那需要看数据存放的顺序,内存满足多插少补的原则。
接下来我就结合代码和调试信息来说明内存粒度对齐的相关问题:
以下提到的测试数据,基于x86
eg1:
typedef struct _STRUCT_A_ { char a; int b; char c; }STRUCT_A;
面对以上的一个结构体,初学者可能认为是1+4+1总共6个字节。那么我们来实际看看它的大小究竟是多少
它是12字节的大小,那么我们再来看看它的内存分布情况:
确实初始化为12个字节的0,那我们赋值字符a,int值5以及字符c之后,内存中又是如何存放的呢?
那我们可以如何优化这个结构体,让他它占有更少的内存呢?我们可以写成以下两种形式:
eg2:
typedef struct _STRUCT_A_ { int b; char a; char c; }STRUCT_A; typedef struct _STRUCT_A_ { char a; char c; int b; }STRUCT_A;
我们再来看看内存中的情况以及结构体大小为多少:
以上证实了我们一开始的说法,当char存放占4字节基准为1字节,接下来的成员还是char,拿它就可以继续存放,存放第二个char之后,还有2字节空余,但是遇到了4字节的int,不够了,只能存放在另外的4字节内存中了。
那我们有没有办法,就是不管我们怎么存放,让它都有固定的内存对齐粒度呢?当然是可以的:
eg3:
#pragma pack(push) #pragma pack(2) typedef struct _STRUCT_A_ { char a; int b; char c; }STRUCT_A; #pragma pack(pop)
我们可以使用以上代码,使内存粒度对齐按照2字节对齐,我们来验证一下:
当然也可以按照1字节对齐,只需要修改pack中的值为1,如下:
eg4:
#pragma pack(push) #pragma pack(1) typedef struct _STRUCT_A_ { char a; int b; char c; }STRUCT_A; #pragma pack(pop)
但是我们在实际情况中,可能会遇到更加复杂点的情况,比如结构体的成员是结构体或者联合体。这种情况又是如何对齐的呢?我们来看一个稍微复杂一点的结构体:
eg5:
typedef struct _STRUCT_A_ { uint32_t a; uint32_t b; }STRUCT_A; typedef union _UNION_A_ { char* m1; STRUCT_A m2; }UNION_A; typedef struct _STRUCT_B_ { UNION_A a; UNION_A b; UNION_A c; UNION_A d; uint16_t e;//2字节 }STRUCT_B;
按照我们的想法,那应该UNION_A所占内存大小为8字节,那么STRUCT_B应该按照8字节对齐,那么5个数据成员,大小应该使5*8=40字节,但是实际情况好像并不是这样:
怎么会出现36这个情况呢?原因出现在STRUCT_A中,虽然unionA的大小确实为8字节,但是其8字节的主要原因是STRUCT_A是8字节,那STRUCT_A中是两个4字节的成员,所以内存粒度对齐还是4字节,这样就不难理解最后的STRUCT_B为36字节了,其中前4个成员是32字节,最后一个是2字节,但是按照4字节对齐,所以是36字节。
“山不向我走来,我便向它走去”