首先把结构体中的内存对齐的规则梳理一下:
1、结构体中的第一个成员存放的首地址是零偏移处
2、结构体成员数据存放的首地址必须是有效对齐参数的整数倍,而有效对齐参数是系统默认对齐参数和数据自身对齐参数两者中的最小值
3、结构体的大小必须是所有成员中的最大有效对齐参数的整数倍
4、结构体在内存中是按照顺序存放的
特别注意对齐是相对于结构体变量首地址(即第一个成员存放的首地址)来说的,举个例子:
我们定一个结构体变量如下
struct _test_st
{
uint08 a;
uint32 b;
uint16 c;
}test_st;
结构体变量test_st成员a存放在偏移量为0的地址中,由于对齐规则,b就存放在偏移量为4的地址中,c就存放在偏移量为8的地址中(后面会具体分析)。
接下来进入实战分析。
下面按照系统默认4字节对齐来讲述。
我们定义3个结构体变量:
typedef struct _test_st_1
{
uint08 a1;
uint32 b1;
uint16 c1;
}test_st_1;
typedf struct _test_st_2
{
uint08 a2;
test_st_1 b2;
uint16 c2;
}test_st_2;
typedef struct _test_st_3
{
uint08 a3;
uint64 b3;
uint16 c3;
}test_st_3;
test_st_1 st1;
test_st_2 st2;
test_st_3 st3;
uint08 array[48];
uint08 len_1;
uint08 len_2;
uint08 len_3;
int main(void)
{
uint08 i = 0;
for (i = 0; i < 48; i++)
{
array[i] = i + 1;
}
len_1 = sizeof(st1);
len_2 = sizeof(st2);
len_3 = sizeof(st3);
memcpy((uint08*)st1, (uint08)array, sizeof(st1));
memcpy((uint08*)st2, (uint08)array, sizeof(st2));
memcpy((uint08*)st3, (uint08)array, sizeof(st3));
for (;;)
}
1、分析变量st1
根据规则1,a1被分配到0地址;
b1对齐参数为4,系统默认对齐参数也为4,根据规则2,有效对齐参数为4,因此存放的地址为4的整数倍,因此在a1存放到0地址后,需要填充3个字节,也就是存放到地址4中去,并占据4-7;
c1对齐参数为2,系统默认对齐参数为4,根据规则2,有效对齐参数为2,因此存放到地址8-9;
a1、b1、c1存放后,总长度为1+3+4+2 = 10,根据规则3,结构体的大小必须是所有成员中的最大有效对齐参数的整数倍,这个结构体最大有效对齐参数是b1的,也就是4,所以需要再次填充2个字节;
综上,变量st1占用了12字节,在内存中的存放方式如下:
2、分析变量st2
st2中嵌套了一个结构体,这个时候又是怎样的?如下:
a2被分配到0地址;
b2大小是12字节,有效对齐参数为4,因此从地址4开始,占用地址为4-15;
c2有效对齐参数为2,因此暂用地址16-17;
根据规则3,需要填充2个字节,使得变量st2总长度为20(最大的有效对齐参数是b2的4字节),它们在内存中的存放方式如下:
3、分析变量st3
按照上面1、2的分析,我们很快知道a3分配地址0,b3(对齐参数应该为系统默认的4)分配地址4-11,c3分配地址12-13,然后总长为16。但是实际并不是这样,而是分配成如下:
这样看起来,b3的有效对齐参数是8而不是4!
我们看看内存面板值就知道:
b3的值是从0x09开始的,所以实际a3后面实际填充了7个字节!
因此对于规则2,在定义8字节数据(long long int)时候是需要特别注意的,它的有效对齐参数是它本身,而不是取系统默认。
通过以上分析知道,我们在定义结构体的时候,尽量要把占用数据大的成员往后靠,这样可以节省内存空间。