
Essay2026 / 03 / 17
汇编入门-控制代码与汇编间的映射
本文以c语言与x86-64为基底,讲述控制语句的汇编实现。即if-else条件分支语句,a?b :c条件表达式,while循环,do-while循环,for循环,switch-case语句
if-else语句 & 条件表达式
最常见的C内条件分支语句的格式为
if(test-expr)
then-statement
else
else-statement
那么利用goto语句模拟汇编控制流,则转化为
t = test-expr1;
if(t)
goto True;
else-statement
goto Done;
True:
then-statement
Done:
这是使用 条件控制 来进行实现条件分支的操作,它简单通用,但是对于现代CPU而言是低效的。 对于简单的条件分支语句,有一种更加高效的实现方法,即 条件传送 。其思路为先计算条件分支的结果,然后再根据条件传送相应的结果。其高效程度在于无需条件跳转,使得CPU流水线填充更满,运行速度更快。
注:现代CPU具有分支预测能力以优化条件跳转,即在条件判断完成前,预先判断条件结果并执行后续指令。但是一旦预测失败则代表前面的活白干了。
下面为例子
long absdiff(long x,long y)
{
long result;
if(x > y) result = y-x;
else result = x - y;
return result;
}
条件分支实现(使用条件跳转)
x --> %rdi, y --> %rsi
absdiff
comq %rsi,%rdi
jg L1 程序执行的性能瓶颈处,必须知道条件结果后才能开始执行后续指令
movq %rsi,%rax
subq %rdi,%rax
ret
L1:
movq %rdi,%rax
subq %rsi,%rax
ret
条件传递
x --> %rdi , y --> % rsi
absdiff:
movq %rsi,%rax
subq %rdi,%rax
movq $rdi,%rdx
subq %rsi,%rdx
cmpq $rsi,%rdi
cmovge %rdx,%rax 使用条件传送指令,即使条件结果没有出来,CPU流水线依旧可以执行下一条指令
ret
当然,在C里面还有一个使用于条件传送,你可以试一试.即 a = test-expr ? then-expr : else-expr
do-while循环
其通用语句为
do
body-statement
while (test-expr);
翻译为接近汇编控制流的代码如下
Loop:
body-statement
v = text-expr
if(v) goto Loop
下面以具体的c代码作为示范讲解.该代码实现阶乘的运算。你可以自己使用gcc将下面的c代码编译为汇编语言进行验证学习
long fact_do(long n){
long result = 1;
do{
result *= n;
n--;
}while(n > 1);
return result;
}
fact_do: 注释
.LFB0:
movl $1, %eax 令result = 1
.L2:
imulq %rdi, %rax result = result * n
subq $1, %rdi n = n -1
cmpq %1, %rdi n比较1
jg .L2 如果n>1,则跳转至L2
rep ret
while循环
while循环的汇编实现有两种。一种是 jump-to-test形式 直观但效率低。另一种为 guarded-do形式 它是编译器常见的优化形式。 先以C代码模拟这两种汇编的控制流
//源码
while(test-expr)
body-statement
//jump-tp-test形式
//while的直接汇编形式
goto test
loop:
body-statement
test:
t = test-expr
if(t) goto loop;
//guarded-do形式
//将while转变为do-while的形式后再汇编
t = test-expr
if(!t)
goto done;
loop:
body-statement;
t = test-expr;
if(t) goto loop;
done:
注:关于guarded-do为什么比jump-to-test好,因为二者在多次循环下没有差异,而在循环次数较少或者没有循环的情况下,guarded-do因为减少一次跳转操作而性能更高。除此之外,guarded-do形式无论进入循环与否,代码都是顺序执行的状态,而jmp-to-test则有开头跳转至中间的一步,这也带来一点点的性能差异
接下来以实际代码举例
long fact_while(unsigned long n)
{
long result = 1;
while(n > 1){
result *= n;
n--;
}
return result;
}
本处为开启-Og优化级别的汇编代码,为jump-to-test实现
fact_while: 注释
.LFBO:
movl $1,%eax result = 1
jmp .L2 跳转至L2
.L3: 循环体内容标签
imulq %rdi,%rax result = result * n
subq $1,%rdi n = n - 1;
.L2 while条件验证标签
cmpq $1,%rdi 比较n与1
ja .L3 在n>1时跳转至L3
rep ret
本处为开启-O1优化级别的汇编代码,实现为guarded-do
fact_while: 注释
.LFB0:
cmpq $1, %rdi 比较n与1
jbe .L4 n<=1时,跳转至L4
movl $1, %eax result = 1
.L3:
imulq %rdi, %rax result = n * result
subq $1, %rdi n = n - 1
cmpq $1, %rdi 比较n与1
jne .L3 n!=1时,跳转至L3
rep ret
.L4:
movl $1, %eax result = 1
ret
for循环
for循环翻译为汇编的思路为,for转变为while形式。然后按while的两种转变形式翻译为汇编代码
//源码
for(init-expr ; test-expr ; update-expr)
body-statement;
//翻译为while形式
init-expr;
while(test-expr)
body-statement;
update-expr;
//再把while翻译到汇编形式
//下面为guarded-do形式
init-expr;
t = test-expr;
if(!t) goto done;
loop:
body-statement;
update-expr;
t = test-expr;
if(t) goto loop;
done:
循环的continue与break
一个循环的级别汇编结构为,而break就是跳转到done标签,continue就是跳转至loop标签
loop:
body-statement;
break; //等同于 goto done
continue; //等同于 goto loop
t = test-expr
if(t) goto loop
done:
switch-case语句
switch-case语句使得我们可以根据一个索引值进行多重分支(multiway brancing) gcc将根据分支数量与密度来选择将其翻译为哪种形式 分支数量少 通常分支数量小于4的情况下,采用 条件分支链 来汇编
//源码
switch(index){
case 1: body-1;
case 2: body-2; break;
default: body-3;
case 100: body-4;
}
//模拟汇编控制流代码
if(index == 1) goto L1;
if(index == 2) goto L2;
if(index == 100) goto L3;
body-3;
L3:
body-4;
goto done;
L1:
body-1
L2:
body-2;
goto done;
done:
分支数量多,索引值间较为密集 此时采用 跳转表(jump table) 来实现。跳转表就是一个包含有跳转指令的目标
//源码
switch(index){
case 100: body-1;
case 101: body-2;break;
case 103: body-3;break;
case 110: body-4;
default: body-5;break;
}
//模拟汇编控制流代码
static void* jt[11] = {&&Loc_A , &&Loc_B , &&Loc_C
&&Loc_def,&&Loc_def,&&Loc_def,
&&Loc_def,&&Loc_def,&&Loc_def,
&&Loc_def,&&Loc_D}
unsigned t = index - 100;
if(t > 10) goto Loc_def;
goto *jt[t]
Loc_A: body-1
Loc_B: body-2; goto done;
Loc_C: body-3; goto done;
Loc_D: body-5;
Loc_def: body-4; goto done;
分支数量多,但是密度低 此时GCC通常采取 生成决策树策略。由于是根据索引查找跳转目标。那么利用查找算法的思想,如二分查找。先将case值排序,然后构建平衡二叉排序树,然后由此调整汇编
switch(case){
case:-10 body-1;
case:2 body-2;
case:10 body-3;
case:100 body-4;
case:114514 body-5;
default: body-6;
}
//基本思路
if(case == 10) goto Loc_C;
if(case > 10) goto R_B;
L_B:
if(case == 2) goto Loc_B;
if(case == -1) goto Loc_A;
goto Loc_def;
R_B:
if(case == 100) goto Loc_D;
if(case == 114514) goto Loc_E;
goto Loc_def;
Loc_A: body-1;
Loc_B: body-2;
Loc_C: body-3;
Loc_D: body-4;
Loc_E: body-5;
Loc_def: body-6;