条件码
条件码寄存器:
CPU维护一组单个位的寄存器,称为条件码寄存器,包含以下:
CF(Carry Flag): 进位标志ZF(Zero Flag): 零标志SF(Sign Flag): 符号标志OF(Overflow Flag): 溢出标志
设置条件码:
算术指令(如addq, subq)和比较指令(如cmpq,testq)会根据结果设置条件码。
区别在于算数指令会修改目的寄存器的值,而比较指令不会修改任何寄存器的值,只设置条件码。
访问条件码
设置指令:
条件码寄存器不能直接访问,但可以通过设置指令将条件码的值存储到寄存器中:
条件跳转指令:
条件跳转指令根据条件码的值决定是否跳转到指定标签:
条件分支
C语言中的条件分支语句(如if语句)在汇编中通常通过比较指令和条件跳转指令来实现:
if (test-expr)
then-statement
else
else-statement用goto改写以更贴切汇编:
t = test-expr;
if (!t)
goto false;
then-statement
goto done;
false:
else-statement
done:在汇编中实现:
# 计算 test-expr 的值并存储在 %rax 中
cmpq $0, %rax # 比较 test-expr 的值与 0
je false # 如果 test-expr 为 0,跳转到 false 标签
then:
# 执行 then-statement
jmp done # 跳转到 done 标签
false:
# 执行 else-statement
done:条件传送:
出于避免分支预测失败的惩罚考虑,有时会同时计算2个分支的值,再通过分支传送指令(如cmov指令)根据条件码选择性地将结果传送到寄存器:
# 计算 test-expr 的值并存储在 %rax 中
cmpq $0, %rax # 比较 test-expr 的值与 0
movq then-result, %rbx # 计算 then-statement 的结果
movq else-result, %rcx # 计算 else-statement 的结果
cmovz %rcx, %rbx # 如果 test-expr 为 0,选择 else-result,否则选择 then-result
循环
C语言中的循环语句(如while语句)在汇编中通常通过标签和条件跳转指令来实现:
do
body-statement
while (test-expr);用goto改写以更贴切汇编:
start:
body-statement
t = test-expr;
if (t)
goto start;在汇编中实现:
start:
# 执行 body-statement
# 计算 test-expr 的值并存储在 %rax 中
cmpq $0, %rax # 比较 test-expr 的值与 0
jne start # 如果 test-expr 不为 0,跳转回 start 标签对while循环和for循环的实现类似,只需调整标签的位置和跳转条件即可,在此不再赘述。
switch语句
C语言中的switch语句在汇编中通常通过跳转表(jump table)来实现:
void switch_eg(long x, long n, long *dest)
{
long val = x;
switch (n) {
case 100:
val *= 13;
break;
case 102:
val += 10;
/* Fall through */
case 103:
val += 11;
break;
case 104:
case 106:
val *= val;
break;
default:
val = 0;
}
*dest = val;
}用goto改写以更贴切汇编:
void switch_eg(long x, long n,
long *dest)
{
/* Table of code pointers */
static void *jt[7] = {
&loc_A, &loc_def, &loc_B,
&loc_C, &loc_D, &loc_def,
&loc_D
};
// 偏移并且显示范围
// int转unsigned int自动将负数转为很大的正数从而过滤
unsigned long index = n - 100;
long val;
if (index > 6)
goto loc_def;
/* Multilay branch */
goto *jt[index];
loc_A: /* Case 100 */
val = x * 13;
goto done;
loc_B: /* Case 102 */
x = x + 10;
/* Fall through */
loc_C: /* Case 103 */
val = x + 11;
goto done;
loc_D: /* Cases 104, 106 */
val = x * x;
goto done;
loc_def: /* Default case */
val = 0;
done:
*dest = val;
}通过建立跳转表,程序可以在时间内跳转到对应的代码块,而不需要进行多次比较和跳转
建立的跳转表有如下特征:
- 通过偏移与限制,让跳转表的索引从0开始
- 0和最大值之间无效的索引会跳转到默认处理代码块
- 多个case标签对应同一代码块时,跳转表中会有多个相同的地址
- 标签按顺序排列时,没有
break的case标签自然滑下 - 标签不按顺序存储,需要加一个跳转语句跳转到下一个标签
汇编当中编译器会在.s文件写入跳转表的内容:
.L4:
.quad .L9
.quad .L5
.quad .L6
.quad .L7
.quad .L2
.quad .L7
.quad .L8
.quad .L2
.quad .L5通过jmp *L4(,%rdi,8)指令实现跳转到对应的标签,其中%rdi存储了偏移后的索引值,8是因为每个地址占8字节。