CSAPP-Revision-ch03

Posted 用七年单身换个PolyU.CSPhD

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CSAPP-Revision-ch03相关的知识,希望对你有一定的参考价值。

这是昨天的战果。


废话不多说,继续……

跳转指令

设想我们的高级语言程序里是不是经常会有“if else if else”等条件语句,我们通过设置这些条件有时候就需要指令的跳转执行。
比如我们不满足if的条件,就得跳跃,跳到else里面执行,不能顺序执行if里的内容。
我们通过“jmp”来实现这一目的。
直接的jmp指令是无条件跳转的,这种无条件跳转又分为两种类型:
1.直接跳转
比如: jmp *%rax,我们就跳转到%rax寄存器包含的地址上
2.间接跳转
比如:jmp *(%rax),我们读出%rax中的数作为内存值,再去这个内存中读出一个地址才是真正要跳转的地方。【还是同C的指针概念】

但更多的时候我们需要的是有条件的跳转,而值得一提的是有条件跳转是没有间接跳转选项的。有条件跳转如下表所示:

不难发现这串指令和SET很相似,而且常常会配合CMP以及TEST使用。
比如CMP我们得到的其实就是减法的值,对此值配合一个条件跳转。例题如下:
我们比较rsi和rdi,实际上就是比较两个参数x和y。
配合jle,就是假设了 x <= y,跳转到L4位置。

还可以结合(一)的最后一道作业题看一看。

关于跳转指令往深了讲还有一点内容就是指令的编码。
其实主要关注的就是汇编器怎么去寻找我们要跳转到的位置。从上面看出跳转的目标我们用一个符号标号写出,比如“.L4”等。但显然的是最后我们的机器还是要根据实打实的数据地址来寻找跳转目的地。
我们较多采用的是“PC”相对寻址方式。注意PC不是电脑哈,是程序计数器,就是永远指向下一条指令的位置的指针。
当然了,我们也可以采用“绝对”地址的编码。

下面看一个“相对寻址”的例子。
比如一段包含跳转的汇编是这样的:

着重来看跳转指令。
第二行指令,第二个字节03就是十进制的3就是相对寻址的差值。
用3来加上,运行第二行指令时PC值(即应该是下一行为 5),得到的就是8这个要跳转的地方。
再看第5行指令,差值同样是第二个字节f8。
1111 1000说明是个负数,符号位不变其余取反加以为1111 1000 = -8。
运行第五行指令时,PC实际已经指向d,用d + -8 = 5,正是要跳回的地方。

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

在C语言中我们常用“if ---- else ----”结果,但汇编器实际翻译后再被译回C控制流代码会成这样。

相信很多老师都说过,goto很不好,会影响代码质量等等……
但实际上就是机器的直译法。
但,现代的处理器已经不再采用这种低效的“转移控制”的方法了。
现在更高效的就是基于条件的数据传输。

一个典型的例子如下:

我们会先算出两个可能的答案值,最后用一条cmovle指令获取正确结果。
这和之前我们的jmp不同,之前的话按照我们代码逻辑:

cmpq %rdi %rsi
jle .L1
...
...
movl xxx, %rax
ret
.L1
	...
	...
	movl xxx, %rax
	ret

类似上面这样,这就称之为基于条件的“控制转移”。所以就会有我们翻译过来相当于:

if x <= y
	goto CODE
...
...
ret
.CODE
	...
	...
	ret

但基于条件的数据传输也有不利影响,比如这些例子:

计算代价好理解。
第二个也比较好理解,因为我们会先去算出两个结果值,事先不知道p指针是否为空。
第三个话同理,会先算出两个值,而第一步x*=7会改变x,x+=3也会改变,都是有副作用的。

好了,讲完了这些,我们就来学一下那条新鲜的指令。

其实就是CMOV类指令,后边跟上的也是不同的条件码组合。所以为了修改条件我们同样会配合诸如TEST、CMP之类的指令。
如果满足条件码情况就把数从SOURCE搬运到DEST,和MOV一样执行。

循环

这并不是什么难得玩意儿,这块已经是对前面的指令的综合运用了。
循环是我们写代码必不可少的工具,种类也很多:
while、for、do while。
比如说看下面一个例子:

先把要返回的值设为0。
有一个符号标志L2.
标志里面将参数rdi放到rdx中。
让edx和1做与运算,结果放到edx中。
让返回结果加上与运算结果值。
做逻辑右移运算,此处是最近更新标志位的操作。
会判断标记位是否为0来判定是否继续上述操作。

因此上述符合 do while结构,因为第一遍是无论如何都要做的,可以写成下面样子的C代码:

long pcount_do
  (unsigned long x) 
  long result = 0;
  do 
    result += x & 0x1;
    x >>= 1;
   while (x);
  return result;


根据上述我们可以总结出一般的翻译“Do–while–”的方式:

接下来看一看While结构的一般形式。
第一个是Goto版本的翻译:

可以说是有点复杂的,和我们的逻辑比起来仿佛是绕了一个大圈。
先无条件跳转到判断语句,判断通过进行循环主体执行。
这个就可以和“do while”联动记忆,因为相比而言后者就是第一步省略了判断,就是没有第一个goto罢了。

当然了还有第二种翻译方式,如下:

我们的第一种方式第一个goto是进入测试环节。
第二种第一个goto则是拿来判断第一次循环条件不满足时直接结束。循环体内部也是一旦goto loop不执行时会自动跳出循环。
感觉这个会稍微符合逻辑一点?

最后就是for循环了。
for循环可以仿照while的方式写,如下:

以一个具体的例子来体现的话就是下图;

要是前面的汇编指令一条条好好理解了,例题看懂了,空全填对了,相信这个真的不难哈。同样来做几道作业题:

8.	已知函数loop的C语言代码框架及其过程体对应的汇编代码,根据对应的汇编代码填写C代码中缺失的语句或表达式。
loop:
# on entry: a in %rdi, n in %esi
movl $0, %r8d
movl $0, %ecx
testl %esi, %esi
jle .L3
.L6:
movl (%rdi,%rcx,4), %edx
leal 3(%rdx), %eax
testl %edx, %edx
cmovns %edx, %eax
sarl $2, %eax
addl %eax, %r8d
addq $1, %rcx
cmpl %ecx, %esi
jg .L6
.L3:
movl %r8d, %eax
ret

注意:只能使用C语言代码中的变量(包括n、a、i和sum)
int loop (int a[ ], int n)

int i, sum;
sum = _____;
for (i = ____________; ____________; ____________) 
sum += ______________;

return ____________;


注意最后我们返回值寄存器%rax的值是从%r8d获得的,所以,%r8d初始化时就相当于我们的返回值进行了初始化。
设置 r8d和ecx为0,测试esi就是n,如果 <= 0就会跳到L3,直接结束。
接下来主要看循环体内容。
从后往前看不难发现,决定是否要继续循环是取决于

cmpl %ecx, %esi
jg .L6
所以不难推测,ecx就是我们的i,初始化为0,当i > n时循环就会退出!

从C函数不难看出a是数组首地址,那么:

movl (%rdi,%rcx,4), %edx
这条指令就是
*(数组首地址 + %rcx的值 × 比例因子4) -> edx
从上面已经看出rcx的的值就是i,所以edx存的就是 a[i]

然后我们继续往下看,其实当我们看到这条指令时:

cmovns %edx, %eax
就可以猜测可能是形如 : () ? xx : yy;

让eax保存 a[i] + 3
测试当前a[i],如果是非负的,“ns”代表非负 就另eax保存 a[i]。
即用一个二目运算符先确定出eax的值后,对其进行算术右移2位的操作,加到%r8d上。
自此不难看出%r8d就是我们代码里的sum,所以初始化时也为0。

循环变量rcx每次都是执行指令:

addq $1, %rcx
就是我们常写的 i+=1

emmm,我写得有点乱,见谅,我的答案长这样:

8.	已知函数loop的C语言代码框架及其过程体对应的汇编代码,根据对应的汇编代码填写C代码中缺失的语句或表达式。
loop:
# on entry: a in %rdi, n in %esi
movl $0, %r8d
movl $0, %ecx
testl %esi, %esi
jle .L3
.L6:
movl (%rdi,%rcx,4), %edx
leal 3(%rdx), %eax
testl %edx, %edx
cmovns %edx, %eax
sarl $2, %eax
addl %eax, %r8d
addq $1, %rcx
cmpl %ecx, %esi
jg .L6
.L3:
movl %r8d, %eax
ret

注意:只能使用C语言代码中的变量(包括n、a、i和sum)
int loop (int a[ ], int n)

int i, sum;
sum = 0;
for (i = 0; i <= n; i += 1) 
sum += (a[i] < 0 ? a[i] + 3 : a[i]) >> 2;

return sum;


以上是关于CSAPP-Revision-ch03的主要内容,如果未能解决你的问题,请参考以下文章

CSAPP-Revision-ch03

CSAPP-Revision-ch03

CSAPP-Revision-ch03

CSAPP-Revision-ch03

CSAPP-Revision-ch03---浮点代码目前不知道到底考不考

CSAPP-Revision-ch03---浮点代码目前不知道到底考不考