Java教程

程序的机器级表示(二)

本文主要是介绍程序的机器级表示(二),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

注:以下所有内容均来自开源学习组织DataWhale

程序的机器级表示(二)

1 加载有效地址

**leaq S, D → \rightarrow → Load Effective Address **

注:x86-64位处理器地址长度都是64,因此都是leaq

例如如下指令:

leaq 7(%rdx, %rdx, 4), %rax:表示把有效地址复制到寄存器rax中。

其中7(%rdx, %rdx, 4)表示有效地址(计算方式见程序的机器级表示(一)中4.0.1):形式符合公式 I m m ( r b , r i , s ) → I m m + R [ r b ] + R [ r i ] × s Imm(r_b, r_i, s)\rightarrow Imm+R[r_b]+R[r_i]\times s Imm(rb​,ri​,s)→Imm+R[rb​]+R[ri​]×s;假设rdx中保存的数值为 x x x,那么根据公式则可计算得到有效地址的值为: 7 + % r d x + % r d x × 4 = 7 + 5 x 7+\%rdx+\%rdx \times 4=7+5x 7+%rdx+%rdx×4=7+5x

注意:leaq指令不是去内存地址 7 + 5 x 7+5x 7+5x处读数据,而是将 7 + 5 x 7+5x 7+5x这个值直接写入目的寄存器rax

2 leaq用于运算

例如有C代码如下:

long scale(long x, long y, long z){
	long t = x + 4 * y + 12 * z;
	return t;
}

编译后汇编代码如下:

scale:
	leaq (%rdi, %rsi, 4), %rax
	leaq (%rdx, %rdx, 2), %rdx
	leaq (%rax, %rdx, 4), %rax
	ret

根据寄存器使用惯例,x、y、z分别保存在寄存器rdi、rsi、rdx中。

根据有效地址计算方式可分析上述汇编代码:

leaq (%rdi, %rsi, 4), %rax → \rightarrow → % r d i + 4 × % r s i = x + 4 × y \%rdi+4\times\%rsi=x+4\times y %rdi+4×%rsi=x+4×y( x + 4 × y x+4\times y x+4×y存入了rax)
leaq (%rdx, %rdx, 2), %rdx → \rightarrow → % r d x + 2 × % r d x = z + 2 × z \%rdx+2\times\%rdx=z+2\times z %rdx+2×%rdx=z+2×z ( 3 z 3z 3z存入了rdx,此时 z = 3 z z=3z z=3z)
leaq (%rax, %rdx, 4), %rax → \rightarrow → % r a x + 4 × % r d x \%rax+4\times\%rdx %rax+4×%rdx(这一步x+4y和3z都被视作整体,得到的结果存到rax)

注:比例因子只能是1,2,4,8中的一个,所以要分解12

3 一元操作和二元操作

3.1 一元操作

一元操作指令只有一个操作数,因此该操作数既是源操作数又是目的操作数,操作数可以是寄存器,也可以是内存地址。

指令影响描述
INC DD ← \leftarrow ← D + 1加1
DEC DD ← \leftarrow ← D - 1减1
NEG DD ← − \leftarrow - ←−D取负
NOT DD ← ∼ \leftarrow \sim ←∼D取补

注:取补即按位取反,然后加1。

3.2 二元操作

二元操作指令包含两个操作数,第一个操作数是源操作数,这个操作数可以是立即数、寄存器或内存地址第二个操作数既是源操作数也是目的操作数,这个操作数可以是寄存器或者内存地址,但不能是立即数

指令影响描述
ADD S, DD ← \leftarrow ← D + S
SUB S, DD ← \leftarrow ← D - S
IMUL S, DD ← \leftarrow ← D * S
XOR S, DD ← \leftarrow ← D ^ S异或
OR S, DD ← \leftarrow ← D | S
AND S, DD ← \leftarrow ← D & S

3.3 一个例子

一开始内存及寄存器中保存的数据如下图所示:在这里插入图片描述

有如下指令:

addq %rcx, (%rax)
subq %rdx, 8(%rax)
incq 16(%rax)
subq %rdx, %rax
  • addq %rcx, (%rax):将内存地址0x100内的数据与寄存器rcx相加,二者之和再存储到内存地址0x100处。这条指令执行完毕后,内存地址0x100处所存的数据由0xFF变为0x100

  • subq %rdx, 8(%rax):将内存地址0x108(0x108=0x100+8)内的数据减去寄存器rdx内的数据,得0xAB - 0x3 = 0xA8,存到内存地址0x108处。这时0x108处数据由0xAB变为0xA8。

  • incq 16(%rax):将内存地址0x110(0x100+10(这个10是16进制的,对应十进制的16))的值加1,即0x13+1=0x14。此时,0x110地址处值变为0x14。

  • subq %rdx, %rax:将寄存器rax内的值减寄存器rdx内的值,即0x100 - 0x3 = 0xFD,存入寄存器寄存器rax内,此时,寄存器rax内值由0x100变为0xFD。

4 移位操作

左移指令SALSHL,二者效果相同,都是在右边填零

右移指令

  • 算术右移:SAR,左边填符号位

  • 逻辑右移:SHR,左边填零

指令影响描述
SAL k, DD ← \leftarrow ← D << k左移
SHL k, DD ← \leftarrow ← D << k左移,等同于SAL
SAR k, DD ← \leftarrow ← D >> A _A A​ k算术右移
SHR k, DD ← \leftarrow ← D >> L _L L​ k逻辑右移

移位量k:可以是一个立即数,或者是放在寄存器cl中的数。(移位指令只允许以特定的寄存器cl作为操作数

寄存器cl的长度为8,原则上移位量的编码范围可达 2 8 − 1 2^8-1 28−1 。实际上对于 w 位的操作数进行移位操作,移位量是由寄存器 cl 的低 m 位来决定。

在这里插入图片描述

对于指令salb,当目的操作数为8位,移位量由cl的低3位决定。
在这里插入图片描述
b → \rightarrow → byte,为8位,即 2 3 位 2^3位 23位,对应2进制3位

对于指令salw,移位量由cl的低4位决定

在这里插入图片描述

w → \rightarrow → word,为16位,即 2 4 2^4 24,对应2进制4位

4.1 一个例子

例如有C代码:

long arith(long x, long y, long z){
 	long t1 = x ^ y;
 	long t2 = z * 48;
	long t3 = t1 & 0xF0F0F0F;
	long t4 = t2 - t3;
 	return t4;
7 }

其中long t2 = z * 48;的汇编指令对应:

leaq (%rdx, %rdx, 2), %rax
salq $4, %rax

leaq (%rdx, %rdx, 2), %rax:即rdx中的值×3放到rax中

salq $4, %rax:rax左移四位,即rax中的值×16

通过这两条指令就实现了×48的操作。

5 汇编操作的一些特殊算术指令

指令描述
imulq S有负号全乘法
mulq S无符号全乘法
cqto转化为八字
idivq S有符号除法
divq S无符号除法

6 条件码

ALU 除了执行算术和逻辑运算指令外,还会根据该运算的结果去设置条件码寄存器。如图中所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-necvrnJP-1640185893504)(D:\DataWhale\ComputerSystem\Task 03:程序的机器级表示\条件码.png)]

6.1 条件码寄存器

条件码寄存器由CPU维护,长度是单个比特位。

在这里插入图片描述

  • CF(Carry Flag,进位标志):当CPU最近执行的一条指令最高位发生进位时,CF会被置为1。

  • ZF(Zero Flag,零标志):当操作结果等于零时,ZF会被置为1。

  • SF(Sign Flag,符号标志):当操作结果小于零时,SF会被置为1,大于零会被置为0。

  • OF(Overflow Flag,溢出标志):针对有符号数,最近的操作导致正溢出或负溢出时,OF会被置为1。

6.2 访问条件码

条件码寄存器的值ALU在执行算术和运算指令时写入,下表中这些算术和逻辑运算指令都会改变条件码寄存器的内容。

Unary OperationsBinary OperationsShift Operations
INC DADD S, DSAL k, D
DEC DSUB S, DSHL k, D
NEG DIMUL S, DSAR k, D
OR S, DSHR k, D
XOR S, D
AND S, D

例如:

  • xor指令:进位标志(CF)和溢出标志(OF)会置0

  • inc和dec:溢出标志(OF)和零标志(ZF)会被设置,但不会改变进位标志(CF)

此外还有两类指令可以设置条件码寄存器,但不会改变目的寄存器的值

  • cmp指令:根据两个操作数的差设置条件码寄存器,与减法指令sub类型。
  • test指令:与and指令类似。

6.3 示例

6.3.1 单一条件码判断条件

有C代码如下:

int comp(long a, long b){
	return (a == b);
}

对应汇编代码如下(a in %rdi, b in %rsi):

comp:
	cmpq		%rsi, %rdi
	sete	%al
	movzbl	%al, %eax
	ret

cmpq %rsi, %rdi:当a-b=0时,指令cmp会将零标志位(ZF)设置为1。

sete %al:这条指令意思是将条件码寄存器内ZF的值赋给寄存器al。

最后al赋值给eax并返回。

6.3.2 条件码组合判断

有C代码如下(a in %dil; b in %sil):

int comp(char a, char b){
	return (a < b);
}

对应汇编指令如下:

comp:
	cmpq	%sil, %dil
	setl	%al
	movzbl	%al, %eax
	ret

setl %al:如果a<b,则寄存器al置为1。(setl的l是less的意思)

cmp指令判断小于情况如下:

注:t=a-b:当a<b,t结果为负,符号位SF会被置为1;当出现正溢出或负溢出时,OF会被置为1。

Case1: a<b t<0 → \rightarrow → SF=1 OF=0 SF^OF=1(未发生溢出)

Case2: a>b t>0 → \rightarrow → SF=0 OF=0 SF^OF=0(未发生溢出)

Case3: a<b t>0 → \rightarrow → SF=0 OF=1 SF^OF=1(发生负溢出)

Case3: a>b t<0 → \rightarrow → SF=1 OF=1 SF^OF=1(发生正溢出)

因此,只有当SF^OF结果为1时,a<b才为真。

对于其他情况,同样可以通过条件码的组合来判断:

在这里插入图片描述

对于无符号数,选择的是进位标志(CF)和零标志(ZF)的组合来判断:

在这里插入图片描述

7 跳转指令与传送指令

有如下C代码:

long absdiff_se(long x, long y){
	long result;
	if(x < y){result = y - x;} else{result = x - y;}
	return result;
}

对应汇编代码如下:

absdiff_se:
	cmpq %rsi, %rdi
	jl.L4
	movq %rdi, %rax
	subq %rsi, %rax
	ret
.L4:
	movq %rsi, %rax
	subq %rdi, %rax
	ret

jl.L4会根据上一条cmpq指令设置的符号位标志SF和溢出标志OF的异或结果来判断是否顺序执行或者跳转到L4处执行,是一种跳转指令。当x>y时,指令顺序执行,当x<y时,跳转到L4处执行。

在现代处理器上,if-else机制比较简单通用,但执行效率可能会比较低。因此有另一种替代策略,C代码如下:

long comvdiff_se(long x, long y){
    long rval = y - x; long eval = x - y;
    long ntest = x >= y;
    if(ntest){rval = eval;} 
    return rval;
}

其对应汇编指令如下:

cmovdiff_se:
	movq	%rsi, %rdx
	subq	%rdi, %rdx
	movq	%rdi, %rax
	subq	%rsi, %rax
	cmpq	%rsi, %rdi
	cmovge	%rdx, %rax
	ret

其中cmovge是根据条件码的某种组合来进行有条件的传送数据,是一种条件传送指令。当x>y时,才会执行这一条指令。

  • 基于条件传送指令的代码会比基于跳转指令的代码效率要高

8 循环

while、do-while、for循环都是通过条件测试指令(如cmpq)和跳转指令组合来实现的。

9 switch语句

switch语句是通过跳转表来实现的。

例如:C代码如下:

void switch_eg(long x, long n, long *dest){
    long val = x;
    switch(n){
        case 0: val *= 13; break;
        case 2: val += 10; break;
        case 3: val += 11; break;
        case 4:
        case 6: val += 11; break;
        default: val = 0;
    }
    *dest = val;	
}

其中switch部分对应跳转表:

case4和case6情况相同:在这里插入图片描述

case1和case5情况相同:在这里插入图片描述

switch相比使用一组很长的if-else效率更高。

这篇关于程序的机器级表示(二)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!