首先我们必须要确定计算机在存储时,CPU -> 寄存器 -> 高速缓存 -> 内存 ,按照这样的缓存层次,我们知道不同通用寄存器的大小决定了cpu一次处理的指令大小,而其取决于机器字长,而高速缓存通过cache line来缓存数据,根据不同的数据总线一次性传输不同大小的数据,也就是说计算机内部传输处理数据并非按照我们想像的单字节的存储读取方式,而是按不同字节的chunks来处理,或者说不同的存储粒度。
我先打印出本机不同类型的size,我的机器是x86_64:
printf("char size:%ld\n", sizeof(char)); printf("short size:%ld\n", sizeof(short)); printf("int size:%ld\n", sizeof(int)); printf("long size:%ld\n", sizeof(long)); printf("float size:%ld\n", sizeof(float)); printf("double size:%ld\n", sizeof(double)); char size:1 short size:2 int size:4 long size:8 float size:4 double size:8
接下来说一下内存对齐的规则:
1、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
2、结构(或联合)的整体对齐规则:在数据成员完成各自对齐之后,结构(或联合)本身也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
3、结合1、2可推断:当#pragma pack的n值来确定对齐位数, 等于或超过所有数据成员长度的时候,这个n值的大小将不产生任何效果。
首先,我们来看一下下面的例子:
#include#include #include #include<string.h> #pragma pack(1) typedef struct data {
char a; int b;
short c; }data_t; #pragma pack() data_t* new_data() { data_t* temp = malloc(sizeof(data_t)); memset(temp, 0, sizeof(data_t)); return temp; }
void free_data(data_t* temp)
{
free(temp);
temp = NULL;
} int main(int argc, char** argv) { data_t* temp1 = new_data(); temp1->a = 'a'; temp1->b = 10;
temp1->c = 10; printf("size:%ld\n", sizeof(data_t));
free_data(temp1);
return 0; }
data_t结构体,按照上面得到的类型size,得到的总大小本应该是 1 + 4 + 2= 7个字节。
到底真是情况如何,我分别按照不同的对齐方式来分别查看用gdb观察一下(字节序小端),程序每一步执行时的内存变化(我对变化内存进行红色表示,黄色表示跳过的填充内存)。
以1字节进行对齐的情况(#pragama pack(1)),以规则一进行分析:
temp1指针为0x602010,
temp1->a = 'a'时,min(数据成员长度, 对齐位数) = min(1, 1) = 1,按1字节对齐:
取得offset为0, a 占用0x602010开始1个字节内存:
0x602010: 0x61 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
temp1->b = 10, min(数据成员长度, 对齐位数) = min(4, 1) = 1,按1字节对齐:
取得offset为1, b 占用0x602011开始4个字节内存:
0x602010: 0x61 0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
temp1->c = 10, min(数据成员长度, 对齐位数) = min(2, 1) = 1,按1字节对齐:
取得offset为5, c 占用0x602011开始2个字节内存:
0x602010: 0x61 0x0a 0x00 0x00 0x00 0x0a 0x00 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
此时,一共占用5个字节,这时候再按规则二考虑min(max(sizeof(char), sizeof(int), sizeof(short)), 1) = 1按一字节,不需要进行填充,所以结构体大小就为5,与推论相同。
以2字节进行对齐的情况(#pragama pack(2)),以规则一进行分析:
temp1指针为0x602010,
temp1->a = 'a'时,min(数据成员长度, 对齐位数) = min(1, 2) = 1,按1字节对齐:
取得offset为0, a 占用0x602010开始1个字节内存:
0x602010: 0x61 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
temp1->b = 10时,min(数据成员长度, 对齐位数) = min(4, 2) = 2,按2字节对齐:
跳过offset = 1, 取得offset为2, b 占用0x602012开始4个字节内存:
0x602010: 0x61 0x00 0x0a 0x00 0x00 0x00 0x00 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
temp1->c = 10, min(数据成员长度, 对齐位数) = min(2, 2) = 2,按2字节对齐:
取得offset为6, c 占用0x602016开始2个字节内存:
0x602010: 0x61 0x00 0x0a 0x00 0x00 0x00 0x0a 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
此时,一共占用8个字节,这时候再按规则二考虑min(max(sizeof(char), sizeof(int), sizeof(short)), 2) = 2按二字节对齐,所以不需要填充字节,所以结构体大小就为8。
以4字节进行对齐的情况(#pragama pack(4)),以规则一进行分析:
temp1指针为0x602010,
temp1->a = 'a'时,min(数据成员长度, 对齐位数) = min(1, 4) = 1,按1字节对齐:
取得offset为0, a 占用0x602010开始1个字节内存:
0x602010: 0x61 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
temp1->b = 10时,min(数据成员长度, 对齐位数) = min(4, 4) = 4,按4字节对齐:
取得offset为4, 跳过offset = 1,2,3, b 占用0x602014开始4个字节内存:
0x602010: 0x61 0x00 0x00 0x00 0x0a 0x00 0x00 0x00 0x602018: 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
temp1->c = 10, min(数据成员长度, 对齐位数) = min(2, 4) = 2,按2字节对齐:
取得offset为8, c 占用0x602018开始2个字节内存:
0x602010: 0x61 0x00 0x00 0x00 0x0a 0x00 0x00 0x00 0x602018: 0x0a 0x00 0x00 0x00 0x00 0x00 0x00 0x00
此时,一共占用10个字节,这时候再按规则二考虑min(max(sizeof(char), sizeof(int), sizeof(short)), 4) = 1 按四字节对齐整个结构体,需要填充两个字节,如上面黄色所表示,所以结构体大小就为12个字节。
又因为max(sizeof(char), sizeof(int), sizeof(short)) = 4字节。
根据规则三,所以按8,16等大于四字节对齐的情况下,其实都是按4字节对齐,得到的sizeof(data_t)都为12字节。