C語言控制流對應的匯編語句


最近在看《深入理解計算機系統》,發現匯編挺有趣。

1.條件分支:if語句

下面是一個簡單的ifelse函數:

int absdiff(int x, int y)
{
    if (x < y)
        return y - x;
    else
        return x - y;
}

對這個程序使用如下命令,得到匯編程序,(注意-S選項大寫,並且始終用-O1優化選項)

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

可以看到gcc對改程序的翻譯與書上略有不同:

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來說,只有很容易計算時(如只有一條加法指令),它才使用條件傳送指令。

【題外話】:

下面的語句產生條件傳送的匯編代碼:

int arith(int x){
    return x / 4;
}

使用-O1選項產生匯編代碼如下:

	.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節講到了這個問題,哎,可惜跳過去了。。)

    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形式產生循環代碼,如下求階乘的循環代碼:

int fact_do(int n)
{
    int result = 1;
    do{
        result *= n;
        n = n-1;
    }while(n>1);
    return result;
}

產生匯編如下:

	.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循環的翻譯

// 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):

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.

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

使用goto語句來保證while循環的更新(寫代碼時,直接在continue前加一個i++即可):

while(i < 10){
	if(i&1)
		goto next;
	sum += i;
next:
	i++;
}

3.switch語句

對switch的匯編,GCC會根據開關數量和稀少程度選擇是否使用 跳轉表 來翻譯開關語句。跳轉表是一個數組,表項i是代碼短的地址,其執行時間與開關情況的數量無關。如下switch語句:

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翻譯成匯編為:

	.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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM