寄存器 vs 内存

寄存器内存
位置CPU 内CPU 外
访问速度
容量
成本
表示方式名字地址
地址多种寻址方式

存储层次结构

汇编语言的操作类型

  • 对寄存器数据或内存数据进行算数和逻辑操作
  • 内存与寄存器间、寄存器与寄存器间传递数据
  • 程序执行顺序的转移

数据格式

类型汇编代码后缀大小(字节)
字节b1
w2
双字l4
四字q8

访问信息

一个 x 86-64 的 CPU 包含一组 16 个通用目的寄存器。 当指令以寄存器作为目标时:

  • 生成 1 或 2 字节数字的指令会保持剩下的字节不变
  • 生成 4 字节数字的指令会把高位 4 字节置为 0

特别地,%rsp 用于指示运行时栈的结束位置。

操作数指示符

操作数分为三个类型:

  • 立即数:用 $Imm 表示,比如 $42$0x1F
    • 不同的指令允许的立即数范围不同
  • 寄存器
    • 以下用 表示任意寄存器,用 表示它的值
  • 内存引用
    • 以下用 表示

寻址模式

类型格式数值
立即数\I mm$
寄存器
内存,其中可以省略任意项, 必须是 1、2、4、8 其中之一

数据传送指令

MOV S, D

包括 movb, movw, movl, movq 特别地,movabsq I, R 表示传送绝对的四字,movq 只能以表示为 32 位补码的立即数作为源操作数,movabsq 可以以任意 64 位立即数作为源操作数。

MOVZ & MOVS

MOVZ 会将目的中剩余字节填充为 0,MOVS 会根据源操作的最高位将剩余字节填充 0 或 1。movzbw, movzbl, … 其中不存在 movzlq,原因是效果和 movl 完全相同。 另外,有指令 cltq,总是将 %eax 符号扩展到 %rax,效果和 movslq %eax, %rax 完全一致。

压入或弹出栈数据

pushq

pushq S,将四字压入栈,%rsp -= 8

popq

popq D,将四字弹出栈,%rsp += 8

算数和逻辑操作

以下各指令类都有不同大小操作数的变种(leaq 除外)。

加载有效地址

leaq,相当于取源操作数的地址写入到目的。 它可以用于表示简单的算术操作。比如 leaq 7(%rdx, %rdx, 4), %rax 表示将 %rax 的值设置为

  • ! 目的操作数必须是一个寄存器。

一元操作与二元操作

一元操作描述
INC加 1
DEC减 1
NEG取负
NOT取反
二元操作
ADD
SUB
IMUL
XOR
OR
AND
结果总是存储在目的操作数中。

移位操作

先给出位移量,再给出要移位的数。

  • ! 移位量只允许是一个立即数或存放在 %cl 中,实际移位量由低 m 位决定,其中 是目的操作数的位长。
  • 右移指令:SAR 表示算数右移,SHR 表示逻辑右移

控制

条件码

  • CF:最高位进位标志,用于检测无符号溢出
  • ZF:零标志
  • SF:符号标志,最近的操作结果为负数
  • OF:溢出标志,最近的操作导致了补码溢出(正溢出&负溢出,t=a+b,检测是否 a, b 符号相同,而 t 符号不同)
  • 条件码由算术指令隐含设置,leaq 不设置条件码

CMP

CMP S1, S2 计算 ,设置条件码,但不改变目的寄存器数值。

TEST

TEST S1, S2 计算 ,设置条件码,但不改变目的寄存器数值。

访问条件码

根据条件码设置字节为 0 或 1

SETX 指令,一般在 CMP 指令后使用,后缀表示不同的条件,目的操作数必须是单字节寄存器或内存。

  • ! 有符号数和无符号数的 set 指令不同!
  • 有符号<:setl^
  • 无符号<:setb
  • 其余可以通过逻辑简单推出

跳转

JX 指令,一般在 CMP 指令后使用,后缀表示不同的条件,语法为 。 跳转目标有多种编码方式,比如用目标指令地址与紧跟在跳转指令后的那条指令的地址的差表示或用绝对地址表示。

通过条件跳转实现条件分支

C 语言中的 if-else 语句:

if (test-expr)
	then-statement
else
	else-statement

汇编通常采用以下形式:

t = test-expr;
if (!t)
	goto false;
then-statement
goto done;
false:
	else-statement
done:

通过条件传送来实现条件分支

能更好利用现代处理器的流水线。 CMOVX 指令,源和目的的值可以是 16 位、32 位或 64 位,根据条件码判断是否进行数据传送。 如果两个表达式中任意一个可能产生副作用,就会导致非法行为。

循环

do-while循环
do {
	body-statement
} while (test-expr)
loop:
	body-statement
	t = test-expr
	if (t)
		goto loop
while 循环
while (test-expr)
	body-statement
goto test;
loop:
	body-statement
test:
	t = test-expr
	if (t)
		goto loop;
for 循环
for (init-expr; test-expr; update-expr)
	body-statement
init-expr;
goto test;
loop:
	body-statement
	update-expr;
test:
	t = test-expr;
	if (t)
		goto loop;

switch 语句

使用跳转表实现。 jmp *.L4(,%rdi,8) 间接跳转,表的基地址为 .L4,每一表项占据 8 字节。

过程

运行时栈

  • 从高地址向低地址增长
  • %rsp 存储栈顶地址

转移控制

  • call 指令:将下一条指令的地址 A 压入栈中,将 PC 设置为函数的起始地址
  • ret 指令:从栈中弹出返回地址 A,并将 PC 设置为 A

数据传送

  • ~ 参数少于 7 个时,从左到右依次用 rdi、rsi、rdx、rcx、r8、r9 传递参数
  • 参数大于等于 7 个时,从右向左压入栈中,数据大小向 8 的倍数对齐