简单认识寄存器

寄存器是最靠近CPU的存储器有着更快读写速度,相当于内存的Cache。(Mysql与Redis的感觉)

计算机存储器读写速度比较:寄存器>主存储器(内存)>硬盘。CPU从寄存器读取速度比从内存读取快百倍甚至千倍的速度,当然越快存储空间越小,所以通常会把比较小的数据从内存复制到寄存器计算。

本文只用到3个寄存器其他寄存器不过多介绍,读者可以网上自主查询。此文的位指的是比特位
寄存器可以理解为一个数组,%rax是整个64位长度的数组,而%eax是%rax数组里的32位的数组。

名称为%rax是比较特殊的寄存器,用于存储函数返回值,这源于寄存器使用规范它们与其他整数寄存器没差异。

长度64位 位置64至0 长度32位 位置0至31 长度16位 位置0至15 长度8位 位置0至7 作用
%rax %eax %ax %al 存储返回值
%rdx %edx %dx %dl 存储值
%rbp %esp %bp %bpl 存储栈指针

使用寄存器

汇编指令

汇编指令与机器代码非常接近的表示。与机器码的二进制格式相比,汇编代码的主要特点是它用可读性更好的文本格式表示。常见的的指令格式 指令 操作数 ,大多数的指令有一个或者多个的操作数。

接下来会使用到movl、leal、addl、ret指令。mov、lea、add指令有着多种格式组合最后一个字符表示寄存器大小。
[‘b’,’w’,’l’,’q’] b表示1个字节大小,w表示2个字节大小,l表示4个字节大小,q表示8个字节大小。

指令 格式 作用
mov mov 源操作数 目标操作数 mov指令操作数有内存,寄存器,mov指令会把数据从源操作数的值复制到目标操作数的位置,不会改变原值。
add add 源操作数 目标操作数 add指令操作数有内存,寄存器,add指令会把数据从目标操作数添加到源操作数。
lea lea 源操作数 目标操作数 lea 会把原操作数当作一个有效地址返回给目标操作数
ret ret 把**%rax**寄存器返回

访问寄存器

访问寄存器有很多组合,(rax)表示获取寄存器的存储值。1(rax)表示寄存器的值+1。

传输数据到寄存器

常用传输数据指令mov和leal,leal只是一个mov变种,可以观察以下代码。b获取到a的有效引用

1
2
int a=10;
int *b=&a;

解析

高级语言是低级语言的一种抽象。

为了更深入计算机是怎么执行代码,以下代码会编译成(汇编指令/JVM指令)

1
2
3
4
5
6
int test()
{
int i = 0;
int b = // i++ Or ++i
return b;
}

C语言

计算用到了栈这种结构(栈有先进后出的特性),栈其实也是内存。
使用栈之前,会向内存申请一片足够计算的空间。%rbp会指向内存地址尾部地址。
例如:一个栈申请了64位的空间内存。那么内存地址就为0x100至0x107(每个地址记录8位内存) %rbp会存储0x107,
往栈放入一个存储一个2字大小的整数x,相当于[(%rbp)-4]=x,弹出栈相当于把%rbp+4回到0x107。

以下是删减汇编代码(为了更直接明了)

i++ 前自增

1
2
3
4
5
6
7
8
movl	$0, -4(%rbp)     //常量0放入栈    栈: 顶[0]
movl -4(%rbp), %eax //栈顶的0复制到%eax 寄存器:{%eax}=0
leal 1(%rax), %edx //%eax其实就%rax的32位, 寄存器:{%edx}={%edx}+1,{%edx}=1
movl %edx, -4(%rbp) //把%edx的值放入栈 栈: 顶[1]
movl %eax, -8(%rbp) //把%eax的值放入栈 栈: 顶[1,0]
movl -8(%rbp), %eax //栈第二个元素复制到%rax返回寄存器
//省略代码
ret //函数返回 这时%rax等于0,所以返回0

++i 后自增

1
2
3
4
5
6
7
movl	$0, -4(%rbp)    //常量0放入栈    栈: 顶[0]
addl $1, -4(%rbp) //栈顶元素的值+1 栈: 顶[1]
movl -4(%rbp), %eax //栈第一个元素复制到%rax返回寄存器
movl %eax, -8(%rbp) //%eax 复制到第二个栈提供给b计算 顶[1,1]
movl -8(%rbp), %eax //栈第二个元素复制到%rax返回寄存器
//省略代码
ret //函数返回 这时%rax等于1,所以返回1

Java

istore_1,iload_1指令这里的1指的是局部变量数组的索引

Index:0 Index:1 index:2
This(对象实例) i b

i++ 前自增

1
2
3
4
5
6
7
8
9
10
11
12
public int test();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0 //往栈放入0。 栈: 顶[0]
1: istore_1 //从栈弹出栈顶放入第一个变量也就是i。 i=0
2: iload_1 //把i的值放入栈 栈: 顶[0] (这里与++i不一样的是i++先放入栈再自增)
3: iinc 1, 1 //把局部变量[1]+1。i=i+1
6: istore_2 //栈弹出并复制给b,b=0
7: iload_2 // 把b的值放入栈 栈: 顶[0]
8: ireturn //返回栈顶的值

++i 后自增

1
2
3
4
5
6
7
8
9
10
11
12
public int test();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=1, locals=3, args_size=1
0: iconst_0 //往栈放入0。 栈: 顶[0]
1: istore_1 //从栈弹出栈顶放入第一个变量也就是i。 i=0
2: iinc 1, 1 //把局部变量[1]+1。i=i+1 (这里与i++不一样的是++先自增再放入栈)
5: iload_1 //把i的值放入栈 栈: 顶[1]
6: istore_2 //栈弹出并复制给b。b=1
7: iload_2 //把b的值放入栈 栈: 顶[1]
8: ireturn //返回栈顶的值

补充

c语言读者如果使用window可以下载Mingw64使用GCC编译成汇编代码,Linux系统自带。
执行命令 gcc -S 目标文件.c

java语言读者可以使用官方自带javap -v 目标.class 查看字节码指令。


参考《CSAPP》,《JVM官方规范》