VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > 汇编语言 >
  • C语言控制流对应的汇编语句

最近在看《深入理解计算机系统》,发现汇编挺有趣。

1.条件分支:if语句

下面是一个简单的ifelse函数:

1
2
3
4
5
6
7
int absdiff(int x, int y)
{
    if (x < y)
        return y - x;
    else
        return x - y;
}

对这个程序使用如下命令,得到汇编程序,(注意-S选项大写,并且始终用-O1优化选项)

1
gcc -S ifelse.c -o ifelse.s –O1

可以看到gcc对改程序的翻译与书上略有不同:

1
2
3
4
5
6
7
8
9
10
11
12
pushl   %ebx
    .cfi_def_cfa_offset 8
    .cfi_offset 3, -8
    movl    8(%esp), %ecx
    movl    12(%esp), %edx
    movl    %edx, %eax
    subl    %ecx, %eax
    movl    %ecx, %ebx
    subl    %edx, %ebx
    cmpl    %edx, %ecx
    cmovge  %ebx, %eax
    popl    %ebx

gcc中,%ecx: x, %edx:y , %eax: y-x, %ebx: x-y. 比较x与y,若x>=y, %eax: x-y. 最终在%eax中存放result。

其中,cmovge使用了后面将要讲到的 条件传送指令,即先计算一个条件操作的两种结果,然后再根据条件是否满足而选取一个。它要求处理器类型在i686以上,在gcc中可以添加'-march=i686'来编译,但是ubuntu11.10的处理器类型就是i686的(使用uname –p查看),所以上面的编译直接得到采用条件传送指令的汇编代码。

使用条件传送并不总是能改进代码效率,对GCC来说,只有很容易计算时(如只有一条加法指令),它才使用条件传送指令。

【题外话】:

下面的语句产生条件传送的汇编代码:

1
2
3
int arith(int x){
    return x / 4;
}

使用-O1选项产生汇编代码如下:

1
2
3
4
5
6
7
8
.cfi_startproc
movl    4(%esp), %eax   //get x
leal    3(%eax), %edx   //temp = x+3
testl   %eax, %eax 
cmovs   %edx, %eax  //if(x < 0) x = temp
sarl    $2, %eax    // return x >> 2
ret
.cfi_endproc

可以看到,如果是负数,在算术右移时,要加上2^k-1=3的偏置。注意,这里加偏置的原因:一般来说,我们可以直接对补码进行右移操作表示2^k幂,但是真正的除法与补码右移还是有一定区别的:

真正除法一定是舍入到0,所以-2.5得到-2;补码右移则会向下舍入,所以-2.5会得到-3(因为它总是把低位丢弃)

所以,在做真正除法时会加上一个偏置值,(原来CS:APP第65页2.3.7节讲到了这个问题,哎,可惜跳过去了。。)

1
2
3
int i = -9;
cout << i/4 << endl;    //get -2
cout << (i>>2) << endl;     //get -3

-9的右移过程如下:得到原码1001——转为补码0111——右移两位1101——转为原码0011,即得到-3。

-9+偏置3过程: -6原码 0110——转为补码1010——右移两位1110——转为原码0010,得到-2.

2.循环

2.1 do-while循环的翻译

汇编中的循环使用 条件测试和跳转 组合起来实现。大部分编译器根据do-while形式产生循环代码,如下求阶乘的循环代码:

1
2
3
4
5
6
7
8
9
int fact_do(int n)
{
    int result = 1;
    do{
        result *= n;
        n = n-1;
    }while(n>1);
    return result;
}

产生汇编如下:

1
2
3
4
5
6
7
8
9
10
11
    .cfi_startproc
    movl    4(%esp), %edx   //get n
    movl    $1, %eax        //set result=1
.L2:
    imull   %edx, %eax      // result *= n
    subl    $1, %edx        //n--
    cmpl    $1, %edx        //compare n-1
    jg  .L2             //if(n>1): goto .L2
    rep
    ret
    .cfi_endproc

2.2 for循环的翻译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// Step1: for循环语句
for(init-expr; test-expr; update-expr)
    body-statement;
 
// Step2: while循环语句
init-expr;
while(test-expr){
    body-statement;
    update-expr;
}
 
// Step3: do-while循环语句
init-expr;
if(!test-expr)
    goto done
do{
    body-statement;
    update-expr;
}while(test-expr);
done:
 
// Step4: goto语句(直观的展示了汇编代码实现)
init-expr;
if(!test-expr)
    goto done
loop:
    body-statement;
    update-expr;
    if(test-expr)
        goto loop;
done:

带continue语句时的特例(练习3.24):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
i = 0;
while(i < 10){
    if(i&1)
        continue;   //continue在i++之前,阻止了i的更新
    sum += i;
    i++;
}
 
i = 0;
if(i >= 10)
    goto done
do{
    if(i&1)
        continue;   //continue在i++之前,阻止了i的更新
    sum += i;
    i++;
}while(i < 10);
done:

do-while循环的continue语句还有一个问题要注意:
翻译为do-while循环时出现了问题,关键是continue的含义是不执行循环体内的内容,直接到达下一个循环点(也就是while处的判断,而不是“do{”处),所以下面语句只会输出1.

1
2
3
4
5
6
7
int i = 1;
do{
    printf("%d\n", i);
    i++;
    if(i<15)
        continue;
}while(0);

使用goto语句来保证while循环的更新(写代码时,直接在continue前加一个i++即可):

1
2
3
4
5
6
7
while(i < 10){
    if(i&1)
        goto next;
    sum += i;
next:
    i++;
}

3.switch语句

对switch的汇编,GCC会根据开关数量和稀少程度选择是否使用 跳转表 来翻译开关语句。跳转表是一个数组,表项i是代码短的地址,其执行时间与开关情况的数量无关。如下switch语句:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int switch_eg(int x, int n){
    int result = x;
    switch(n){
        case 100:
            result *= 13;
            break;
        case 102:
            result += 10;
        case 103:
            result += 11;
            break;
        case 104:
        case 106:
            result *= result;
            break;
        default:
            result = 0;
    }
    return result;
}

使用-O1翻译成汇编为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
    .cfi_startproc
    movl    4(%esp), %eax
    movl    8(%esp), %edx
    subl    $100, %edx
    cmpl    $6, %edx
    ja  .L8
    jmp *.L7(,%edx,4)
    .section    .rodata
    .align 4
    .align 4
.L7:
    .long   .L3
    .long   .L8       //case 101: default
    .long   .L4
    .long   .L5
    .long   .L6
    .long   .L8       //case 105: default
    .long   .L6
    .text
.L3:                                     //case 100: result *= 13
    leal    (%eax,%eax,2), %edx   // get 3*x
    leal    (%eax,%edx,4), %eax   //get x+4*(3x)= 13*x
    ret
.L4:                                     //case 102: result += 10
    addl    $10, %eax
.L5:                                     //case 103: result += 11
    addl    $11, %eax
    ret
.L6:                                     //case 104/106: result *= result
    imull   %eax, %eax
    ret
.L8:                                     //default: result = 0
    movl    $0, %eax
    ret
    .cfi_endproc

出处:https://www.cnblogs.com/dandingyy/archive/2013/01/03/2837053.html


相关教程