写在前面:从腾讯实习回来之后,就感觉到自己的知识体系过于散乱。于是萌生了写一个自己的操作系统这样的心思,此为系列第一章,主要是讲解一些汇编知识的,内容大多从CSAPP中也可以获得。
本篇内容主要讲解:结构体和联合体的汇编实现
C语言中用struct
来声明一个结构体,将可能不同类型的对象聚合到一个对象指针,用名字来引用结构的各个组成部分。
像数组一样,结构体的所有组成部分都存放在内存中一段连续的区域内,而指向结构的指针就是结构第一个字节的地址。编译器维护关于每个结构类型的信息,指示每个字段的字节偏移。同时,它以这些偏移作为内存引用指令中的唯一,从而产生对结构元素的引用。
让我们来看下面这个例子,考虑这样的结构声明:
struct rec { int i; int j; int a[2]; int *p; };
这个结构体在内存中的摆放便如下所示:
为了访问结构的字段,编译器产生的代码要将结构的地址加上适当的偏移。例如,假设struct rec*
类型的变量 r
放在寄存器%rdi
中,那么下面的汇编代码,会让r->j = r->i
。
movl (%rdi), %eax //%rax = r->i movl %eax, 4(%rdi) //r->j = %rax
那么我们再复杂一点,计算下面这里表达式的汇编代码:
r->p =&r->a [r->i+r->j];
其汇编代码如下:
//Registers: r in %rdi movl 4(%rdi), %eax //%rax = r->j addl (%rdi), %eax //%rax += r->i cltq //扩展成 8字节 leaq 8(%rdi,%rax,4), %rax //%rax = %rax*4 + %rdi + 8-->&r->a[r->i+r->j] movq %rax, 16(%rdi) //r->p = %rax
int result = 0; while(ptr != nullptr) { result += ptr->val; ptr = ptr->next; } return result;
联合提供了一种方式,能够规避C语言的类型系统,允许以多种类型类似引用一个对象。比如下面的联合体声明:
typedef union U3{ struct{ //这个结构体大小11 long u; short v; char w; }t1; struct{ //这个结构体大小16字节 int a[2]; char *p; }t2; }up;
而这个类型在内存中便只有最长的元素的空间:
其访问跟结构体一样,直接使用偏移量来表示各个元素,这里就不举例了。
许多计算机体系对基本数据类型的合法地址做出了一些限制,要求某种类型的对象的地址必须是某个值K(K = 2、4、8)
的倍数,而这就是内存对齐!
那么为什么要对齐呢?
假设一个处理器总是从内存中取8字节,则地址必须为8的倍数。如果我们能够保证将所有的double
类型数据的地址对齐成8的倍数,那么就可以用一个内存操作来读写值。否则,我们可能需要执行两次内存访问,因为对象可能被放在两个8字节的内存块中。很显然,对齐可以大大提高内存系统的性能。
而在汇编中,我们可以使用下面的命令指定内存对齐方式:
.align 8 //8字节内存对齐
而如果按照8字节来对齐的,下面的数据结构在内存中就会改变其内存数据。
struct S { int i; int j; char c; };
那么假如说程序定义为这样呢?
struct S { int i; char c; int j; };
程序布局便成为如下的布局了
[1] 深入理解计算机系统 第三章