X86-64 下的通用寄存器与汇编指令----初步
X86-32 与 X86-64 的数据类型宽度
Size of C Objects (in Bytes)
C Data TypeTypical | 32-bit | Intel IA32 | x86-64 |
---|---|---|---|
unsigned | 4 | 4 | 4 |
int | 4 | 4 | 4 |
long int | 4 | 4 | 8 |
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
float | 4 | 4 | 4 |
double | 8 | 8 | 8 |
long double | 8 | 10/12 | 16 |
char * | 4 | 4 | 8 |
大部分是一样的, 我们着重关注不一样的.
long int 在 IAI32 下是 4, 在 64 位下是 8
long double 也不一样, 但这个我们一般不用
后面只要是指针类型, 那么 32 位情况下肯定是 4 个 byte, 64 位下肯定是 8 个 byte
X86-64 的通用寄存器
通用寄存器前缀从 e 改成 r, 扩展成 64 位
为了保证向下兼容, 64 位模式下, 还可以用 eax edx ebp 这种方式来进行 32 位操作数的存取, 这个没有任何问题
图片右侧就是它新扩展的 8 个 64 位寄存器. 这新寄存器的低 32 位也都给了名字, 这样你在指令里面也可以用它的名字来访问它的低 32 位
同时这里面还要再说一下, 就是在 X86-64, 16 个通用寄存器里面的这个 ebp, 还有 rdp 都被彻底解放出来了. 就是说 %ebp/%rbp 不再是专用的寄存器了, 这个我们在讲 64 为运行栈的时候着重说一下这个问题.
x86-64 下的 swap()
void swap(int *xp, int *yp)
{
int t0 = *xp;
int t1 = *yp;
*xp = t1;
*yp = t0;
}
我们用 gcc 编译出 64 位的汇编代码 :
swap :
movl (%rdi), %edx
movl (%rsi), %eax
movl %eax, (%rdi)
movl %edx, (%rsi)
retq
我们和 32 位的代码比较, 很容易就发现不一样.
我们可以看到, 在 64 位的情况下, 寄存器数量多了, 那就是说我的寄存器要充分使用, 因为用寄存器比用内存要快一些.
64 位的不同点就是利用寄存器来传递参数, 而 32 位情况下默认使用栈内存来传递参数.
64 位情况下, 当参数小于 7 个时候, 参数从左到右放入寄存器 : rdi, rsi, rdx, rcx, r8, r9. 当参数为 7 个或者以上的时候, 前面 6 个传送方式不变, 但后面的参数是依次从 "右向左" 放到栈内存中.
一般情况下, 函数里局部变量不是太多的话, 编译器偏好于把它分配到寄存器里面, 避免访问内存.
movl (%rdi), %edx
就是把 (xp) 这个地方的数据取出来, 放到 edx 里面, 由于我这个数据本身是 int 类型, 所以是用的 movl
.
movl (%rsi), %eax
就是把 (yp) 这个内存的数据取出来放到 eax 里面去, 然后两者互换一下, 放回去, 这个函数就完了.
这个 64 位代码比 32 位代码要精简很多, 因为它很大程度上避免了对栈的操作.
X86-64 下 long int 类型的 swap()
我们把刚才函数变形一下, 把它参数类型变为 long int, 就是 64 位数据, 其它不变.
void swap(long int *xp, long int *yp)
{
long int t0 = *xp;
long int t1 = *yp;
*xp = t1;
*yp = t0;
}
编译成 64 位汇编代码如下 :
swap_1 :
movq (%rdi), %rdx
movq (%rsi), %rax
movq %rax, (%rdi)
movq %rdx, (%rdi)
retq
在这种情况下, 因为被操作的数据是 64 位, 所以使用寄存器 %rax %rdx, 以及把指令换成了 movq
. movq
的后缀 q 表示 "4字". 除此之外, 指令和刚才的基本类似.
小结
X86 指令的特点
- 支持多种类型的指令操作数
立即数, 寄存器, 内存数据
-
算逻指令可以以内存数据作为操作数
-
支持多种内存地址计算模式
详见之前寻址模式
也可以用于整数计算 (如leal
指令) -
支持变长指令 从 1~15 字节
X86 汇编的格式
补充内容
实际上 gcc 也可以让它生成 intel 汇编的语法