C语言中的struct会做内存对齐,这个已经是被广为人知的,但是网上对于内存对齐的原因的解释却比较少,很多资料仅仅是说为了“提高读写效率",这个理由其实还是很难有说服力。
这里提供一个新的解释思路,就是我们要知道struct 是可以直接做赋值的,利用= 符号可以直接做深拷贝。
typedef struct Foo Foo;
struct Foo {
int a;
short b;
char c;
};
int main () {
Foo X = {1, 2, 'a'};
Foo Y = X;
Y.c = 'c';
printf("X.c = %c, Y.c = %c\n", X.c, Y.c);
// X.c = a, Y.c = c
return 0;
}
然后我们来思考一下,这个深拷贝怎么去实现。
如果有内存对齐:
这个struct Foo ,根据内存对齐的知识,我们知道sizeof(Foo) = 8 ,而x86上的寄存器位宽最大就是8,因此我们只需要读写一次就可以完成Foo Y = X 这样的深拷贝了。
我们把上面的代码的汇编打印出来,可以看到下面的x86汇编:
movl $1, -8(%rbp)
movw $2, -4(%rbp)
movb $97, -2(%rbp)
movq -8(%rbp), %rax
movq %rax, -16(%rbp)
上面的三行代码对应的是Foo X = {1, 2, 'a'} ,最后两行代码就是Foo Y = X; ,可以看到在汇编中第4行直接使用%rax 寄存器访存,然后再向-16(%rbp) 的地址处写入8字节。
如果没有内存对齐
?实际上,我们也可以要求C语言不做内存对齐的,将上面的struct Foo 修改一下:
struct __attribute__((__packed__)) Foo {
int a;
short b;
char c;
};
这个时候要完成一次深拷贝要稍微复杂一点,不能直接利用一个%rax 寄存器,因为此时sizeof(Foo) = 7 ,而%rax 的位宽是8,像之前的汇编代码那样做,会直接写越界。为了保证不会越界写,编译器就得一个变量一个变量地去写(对于上面的代码来说),也就是说,这里需要做三次读写。
我们再次编译上面的代码来看看:
movl $1, -7(%rbp)
movw $2, -3(%rbp)
movb $97, -1(%rbp)
movl -7(%rbp), %eax
movl %eax, -14(%rbp)
movzwl -3(%rbp), %eax
movw %ax, -10(%rbp)
movzbl -1(%rbp), %eax
movb %al, -8(%rbp)
头三行仍然是对应Foo X = {1, 2, 'a'} ,但是可以看出Foo Y = X; 多了不少代码,第4,5两行实际上是Y.a = X.a ,第6,7行实际上就是Y.b = X.b ,紧接着第8,9两行就是Y.c = X.c 。
可以看出,如果没有内存对齐,struct 做深拷贝的效率将会降低不少。
|