3.6.5 用条件控制来实现条件分支
3.6.5 用条件控制来实现条件分支
将条件表达式和语句从 C 语言翻译成机器代码,最常用的方式是结合有条件和无条件跳转。(另一种方式在 3.6.6 节中会看到,有些条件可以用数据的条件转移实现,而不是用控制的条件转移来实现。)例如,图 3-16a 给出了一个计算两数之差绝对值的函数的 C 代码1。这个函数有一个副作用,会增加两个计数器,编码为全局变量 lt_cnt 和 ge_cnt 之一。GCC 产生的汇编代码如图 3-16c 所示。把这个机器代码再转换成 C 语言,我们称之为函数 gotodiff_se(图 3-16b)。它使用了 C 语言中的 goto 语句,这个语句类似于汇编代码中的无条件跳转。使用 goto 语句通常认为是一种不好的编程风格,因为它会使代码非常难以阅读和调试。本文中使用 goto 语句,是为了构造描述汇编代码程序控制流的 C 程序。我们称这样的编程风格为“goto 代码”。
1 实际上,如果一个减法溢出,这个函数就会返回一个负数值。这里我们主要是为了展示机器代码,而不是实现代码的健壮性。
在 goto 代码中(图 3-16b),第 5 行中的 goto x_ge_y 语句会导致跳转到第 9 行中的标号 x_ge_y 处(当 x >= y 时会进行跳转)。从这一点继续执行,完成函数 absdiff_se 的 else 部分并返回。另一方面,如果测试 x >= y 失败,程序会计算 absdiff_se 的 if 部分指定的步骤并返回。
汇编代码的实现(图 3-16c)首先比较了两个操作数(第 2 行),设置条件码。如果比较的结果表明 x 大于或者等于 y,那么它就会跳转到第 8 行,增加全局变量 ge_cnt,计算 x - y 作为返回值并返回。由此我们可以看到 absdiff_se 对应汇编代码的控制流非常类似于 gotodiff_se 的 goto 代码。
long lt_cnt = 0;
long ge_cnt = 0;
long absdiff_se(long x, long y)
{
long result;
if (x < y) {
lt_cnt++;
result = y - x;
}
else {
ge_cnt++;
result = x - y;
}
return result;
}a)原始的 C 语言代码
1 long gotodiff_se(long x, long y)
2 {
3 long result;
4 if (x >= y)
5 goto x_ge_y;
6 lt_cnt++;
7 result = y - x;
8 return result;
9 x_ge_y:
10 ge_cnt++;
11 result = x - y;
12 return result;
13 }
b)与之等价的 goto 版本
long absdiff_se(long x, long y)
x in %rdi, y in %rsi
1 absdiff_se:
2 cmpq %rsi, %rdi Compare x:y
3 jge .L2 If >=, goto x_ge_y
4 addq $1, lt_cnt(%rip) lt_cnt++
5 movq %rsi, %rax
6 subq %rdi, %rax result = y - x
7 ret Return
8 .L2: x_ge_y:
9 addq $1, ge_cnt(%rip) ge_cnt++
10 movq %rdi, %rax
11 subq %rsi, %rax result = x - y
12 ret Return
c)产生的汇编代码
图 3-16 条件语句的编译。a)C 过程 absdiff_se 包含一个 if-else 语句;b)C 过程 gotodiff_se 模拟了汇编代码的控制;c)给出了产生的汇编代码
C 语言中的 if-else 语句的通用形式模板如下:
if (test-expr)
then-statement
else
else-statement
这里 test-expr 是一个整数表达式,它的取值为 0(解释为“假”)或者为非 0(解释为“真”)。两个分支语句中(then-statement 或 else-statement)只会执行一个。
对于这种通用形式,汇编实现通常会使用下面这种形式,这里,我们用 C 语法来描述控制流:
t = test-expr;
if (!t)
goto false;
then-statement
goto done;
false:
else-statement
done:
也就是,汇编器为 then-statement 和 else-statement 产生各自的代码块。它会插入条件和无条件分支,以保证能执行正确的代码块。
旁注 用 C 代码描述机器代码
图 3-16 给出了一个示例,用来展示把 C 语言控制结构翻译成机器代码。图中包括示例的 C 函数 a 和由 GCC 生成的汇编代码的注释版本 c,还有一个与汇编代码结构高度一致的 C 语言版本 b。机器代码的 C 语言表示有助于你理解其中的关键点,能引导你理解实际的汇编代码。
练习题 3.16 已知下列 C 代码:
void cond(long a, long *p)
{
if (p && a > *p)
*p = a;
}GCC 会产生下面的汇编代码:
void cond(long a, long *p)
a in %rdi, p in %rsi
cond:
testq %rsi, %rsi
je .L1
cmpq %rdi, (%rsi)
jge .L1
movq %rdi, (%rsi)
.L1:
rep; ret
A. 按照图 3-16b 中所示的风格,用 C 语言写一个 goto 版本,执行同样的计算,并模拟汇编代码的控制流。像示例中那样给汇编代码加上注解可能会有所帮助。
B. 请说明为什么 C 语言代码中只有一个 if 语句,而汇编代码包含两个条件分支。
练习题 3.17 将 if 语句翻译成 goto 代码的另一种可行的规则如下:
t = test-expr;
if (t)
goto true;
else-statement
goto done;
true:
then-statement
done:
A. 基于这种规则,重写 absdiff_se 的 goto 版本。
B. 你能想出选用一种规则而不选用另一种规则的理由吗?
练习题 3.18 从如下形式的 C 语言代码开始:
long test(long x, long y, long z) {
long val = ____________________;
if (____________________) {
if (____________________)
val = ____________________;
else
val = ____________________;
} else if (____________________)
val = ____________________;
return val;
}GCC 产生如下的汇编代码:
long test(long x, long y, long z)
x in %rdi, y in %rsi, z in %rdx
test:
leaq (%rdi,%rsi), %rax
addq %rdx, %rax
cmpq $-3, %rdi
jge .L2
cmpq %rdx, %rsi
jge .L3
movq %rdi, %rax
imulq %rsi, %rax
ret
.L3:
movq %rsi, %rax
imulq %rdx, %rax
ret
.L2:
cmpq $2, %rdi
jle .L4
movq %rdi, %rax
imulq %rdx, %rax
.L4:
rep; ret
填写 C 代码中缺失的表达式。