阿月很乖

念念不忘,必有回响

  menu
99 文章
114 评论
148913 浏览
3 当前访客
ღゝ◡╹)ノ❤️

[阅读] CSAPP 读书笔记 —— 程序的机器级表示(二)

上一部分主要说到了栈的压入和弹出,接下来继续的就是一些操作符的使用

算术或逻辑操作

caozuo

加载有效地址

leaq 是 movq 指令的变形,从内存读数据到寄存器。

  • 源操作数:看上去是内存引用,但是得到有效地址
  • 目的操作数:必须是一个寄存器

例:

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

编译后:

# long scale(long x, long y, long z)
# x in %rdi, y in %rsi, z in %rdx
scale:
	leaq	(%rdi, %rsi, 4), %rax	# x + 4 * y
	leaq	(%rdx, %rdx, 2), %rdx	# z + 2 * z = 3 * z
	leaq	(%rax, %rdx, 4), %rax	# (x+4*y) + 4*(3*z) = x + 4 * y + 12 * z
	ret

一元/二元 操作

一元操作,例如第二组

  • 只有一个操作数,即是源又是目的
  • 可以是一个寄存器,也可以是一个内存地址

二元操作,例如第三组

  • 第二个操作数即是源又是目的

  • 第一个操作数可以是立即数、寄存器或内存地址

  • 第二个操作数可以是寄存器或内存地址

移位操作

左移:SAL / SHL,同效

右移

  • 算术右移:SAR,填上符号位
  • 逻辑右移:SHR,填上 0

特殊的算术操作

八字(128位,16字节)乘积以及整数除法指令

指令

控制

机器代码提供两种基本的低级机制来实现有条件的行为:测试数据值,然后根据测试的结果来改变控制流或者数据流。

对于条件分支有两种不同的实现方式

  1. 用条件控制来实现条件分支
  2. 用条件传送来实现条件分支

条件码

条件码寄存器,最常用的条件码有:

  • CE:进位标志
  • ZF:零标志
  • SF:符号标志
  • OF:溢出标志

leaq 指令不改变任何条件码

CMP 和 TEST 指令只设置条件码而不改变任何其他寄存器。

指令基于描述
CMP S1, S2
cmpb
cmpw
cmpl
cmpq

TEST S1, S2
testb
testw
testl
testq
S2 - S1





S2 & S1



比较
比较字节
比较字
比较双字
比较四字

测试
测试字节
测试字
测试双字
测试四字

访问条件码

条件码通常不会直接读取,有三种方式访问

  1. 可以根据条件码的某种组合,将一个字节设置为 0 或者 1 —— SET 指令
  2. 可以条件跳转到程序的某个其他的部分
  3. 可以有条件地传送数据

跳转指令

跳转指令

用条件控制来实现条件分支

C 语言中的 if-else 语句的通用形式如下

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

汇编实现通常使用下面的形式,这里,我们使用 C 语法来表述控制流

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

也就是,汇编器为 then-statementelse-statement 产生各自的代码块,他会插入条件和无条件分支,以保证执行正确的代码块。

用条件传送来实现条件分支

使用数据的条件转移,计算一个条件操作的两种结果,然后更具条件是否满足从中选一个。

条件传送指令:

条件传送指令

条件表达式和赋值的通用形式

v = test-expr ? then-expr : else-expr;

用条件控制转移的标准方法来编译这个表达式会得到如下形式:

	if (test-expr)
        goto false;
    v = then-expr;
    goto done;
false:
	v = else-expr;
done:

基于条件传送的代码,会对 then-exprelse-expr 都求值,最终值的选择基于对 test-expr 的求值,可以用以下抽象代码描述

v	=	then-expr;
ve	=	else-expr;
t	=	test-expr;
if (!t)	v = ve;

在 GCC 中,只有当两个表达式都很容易计算时,例如表达式分别都是一条加法指令,他才会使用条件传送。

循环

  1. do-while 循环

    do
        body-statement
        while (test-expr);
    

    汇编

    loop:
    	body-statement
    	t = test-expr;
    	if (t)
            goto loop;
    
  2. while 循环

    while (test-expr)
        body-statement
    

    第一种翻译方法——跳转到中间。执行一个无条件跳转到循环结尾的测试,以此来执行初始的测试。

    	goto test;
    loop:
    	body-statement
    test:
    	t = test-expr;
    	if (t)
            goto loop;
    

    第二种翻译方法——guarded-do。首先使用条件分支,如果初始条件不成立就跳过循环,把代码变化为 do-while 循环。

    翻译成 do-while 循环

    t = test-expr;
    if (!t)
        goto done;
    do
        body-statement
        while (test-expr)
    done;
    

    相应的,可以翻译成 goto 代码

    t = test-expr;
    if (!t)
        goto done;
    loop:
    	body-statement
    	t = test-expr;
    	if (t)
            goto loop;
    done;
    
  3. for 循环

    for (init-expr; test-expr; update-expr)
        body-statement
    

    与下面 while 循环行为一致

    init-expr;
    while (test-expr) {
        body-statement
        update-expr;
    }
    

    GCC 为 for 循环产生的代码是 while 循环的两种翻译之一,这取决于优化的等级,也就是,跳转到中间策略会得到如下代码:

    init-expr;
    goto test;
    loop:
    	body-statement
    	update-expr;
    test:
    	t = test-expr;
    	if (t)
            goto loop;
    

    而 guarded-do 策略得到:

    	init-expr;
    	t = test-expr;
    	if (!t)
            goto done;
    loop:
    	body-statment
    	update-expr;
    	t = test-expr;
    	if (t)
            goto loop;
    done:
    

switch

switch 语句可以根据一个整数索引值进行多重分支。通过使用 跳转表 这种数据结构使得实现更加高效。

代码太多不想写了-0-

念念不忘,必有回响。

PS:如果觉得文章不错或者帮到了您,帮忙点点下面广告呗~谢谢啦~

评论