博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
C语言控制流对应的汇编语句
阅读量:5318 次
发布时间:2019-06-14

本文共 4125 字,大约阅读时间需要 13 分钟。

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

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 donedo{	body-statement;	update-expr;}while(test-expr);done:// Step4: goto语句(直观的展示了汇编代码实现)init-expr;if(!test-expr)	goto doneloop:	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 donedo{	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

转载于:https://www.cnblogs.com/dandingyy/archive/2013/01/03/2837053.html

你可能感兴趣的文章
linux守护进程的编写
查看>>
Quartus prime16.0 与modelsim ae 联调
查看>>
你总说时间很少
查看>>
程序一启动检查网络,如果没有网络就退出程序
查看>>
《DOS命令全集(中英文对照)》CHM版.CHM
查看>>
Check if a string is NULL or EMPTY using PowerShell
查看>>
shell 用环境变量的值修改properties文件
查看>>
SQL SERVER 数据压缩
查看>>
Debian7 apt源设置
查看>>
同域和不同域长啥样
查看>>
css样式优先级计算规则
查看>>
从浏览器地址栏输入url到显示页面的步骤
查看>>
关于神经网络训练的一些建议笔记
查看>>
Win32 窗口篇(3)
查看>>
do { ....} while(0) 在宏里冗余的意义
查看>>
js(jQuery)获取时间的方法及常用时间类
查看>>
6款程序员不得不爱的bootstrap模板
查看>>
使用Visio进行UML建模
查看>>
mongoDB 高级查询之取模查询$mod
查看>>
poj 2886 Who Gets the Most Candies?(线段树)
查看>>