c语言实现二元整数算术运算,C编译器剖析_1.5 结合C语言来学汇编_整数运算

让我们还是从熟悉的加减乘除等算术运算入手。图1.23给出了一个简单的C程序,其中包含了常见的C语言算术运算,我们依旧采取对比C语言代码和UCC编译器生成的汇编代码的方法来讨论。

0818b9ca8b590ca3270a3433284dd417.png

图1.23     arith.c

我们有意在图1.23的第1至4行定义了几个初始化或未初始化的变量。图1.24给出了这几个变量在汇编代码中的区别。在图1.24中,第7行至第9行对应的是初始化为10的全局变量a;而第11至13行对应的是初始化为20的全局变量b;第15行对应的是没有作初始化的全局变量c;而第16行对应的是没有作初始化的静态变量d,为避免重名,被UCC编译器改名为”d.0”;而第17行对应的是初始化为5的静态变量e,被UCC编译器重命名为”e.1”,而且我们注意到汇编代码中不存在”.global  e.1”,但是可在第7和第11行看到对变量a和b的global声明,这代表变量名a,b是在全局可见的,但e.1是静态的,只在当前文件中可见。按照C语言的语义,全局或静态未初始化的变量其缺省值一般为0。在生成目标文件(后缀为.obj或.o的文件)时,我们只需要在目标模块中记录这块要被初始化为0的空间有多大就可以,没有必要把一堆的0存到目标文件中。确切地说,汇编代码中第15行的”.comm”用于声明未初始化的全局变量,comm是common之意,代表这是一块公共用地,即全局变量之意,而第16行的”.lcomm”则用于声明未初始化的静态变量,lcomm是local   common之意,只在本目标模块中可见。

0818b9ca8b590ca3270a3433284dd417.png

图1.24     初始化和未初始化

接下来,让我们看一下图1.23的第5至第17行各C语言算术运算所对应的汇编代码,如下图1.25所示。图1.25中的第32至33行对应“c  = a | b;”, 第32行把a的值从内存加载到寄存器eax中,第33行把内存中的变量b和寄存器eax作按位或运算,结果仍存于eax中,第34行再把运算结果从寄存器eax写回到内存中的变量c中。图1.25第35至49行所做的运算依次为andl,shll,sarl, addl和 subl。其中的and,add和sub对应按位与、加和减运算。而shl是Shift Left的缩写,进行左移运算。右移运算比较特殊,有算术右移sar和逻辑右移shr之分,sar是Shift Arithmetic Right的缩写,而shr则是Shift  Right的缩写,两者的区别是sar右移时最高位要用之前的符号位来填充,而shr右移时最高位补0。

0818b9ca8b590ca3270a3433284dd417.png

图1.25  arith.s

例如,以下代码中函数f()会死循环,而函数g()则不会。原因是:a是有符号整数,C编译器会选用sar汇编指令来进行移位操作,而a的值为-1,在内存中以补码形式存放时即为0xFFFFFFFF,算术右移后仍然是-1。而b为无符号整数,C编译器会选择shr来进行逻辑右移,最高位补入的是0,经过32次的逻辑右移操作后,b的值就变为0了。

int a = -1;

unsigned int b = 0xFFFFFFFF;

void f(){

while(a>>= 1);

}

void g(){

while(b>>= 1);

}

图1.25中的第50至52对应的是”c = a * b;”,其中第51行的imul是有符号整数乘法signed multiple指令,而作无符号整数乘法的指令为mul。第53至第56行对应的是”c = a / b;”,第53行把被除数a存到寄存器eax中,第54行的指令cdq是Change Double word to Quadrateword的缩写,意思是把双字扩展为四字。在16位CPU时代,把一个word定义为2 字节,所以双字对应4字节,四字对应8字节,x86会把寄存器edx和eax共同构成的8个字节当成被除数,edx充当高32位,而eax充当低32位。则经cdq指令后,edx中32位的内容都是eax的最高位。如果寄存器eax最高位为1,edx各bit全为1;否则全为0。不难想象,如果要作无符号数的除法,因为eax的最高位不再被当作符号位,我们需要直接把edx寄存值赋值为0,相应的代码如x86Linux.tpl的第48行所示:

TEMPLATE(X86_DIVU4,    "movl $0, %%edx;divl %2")

图1.25第55行的指令idiv是有符号整数除法signed divide指令,而div则是无符号整数除法指令unsigned divide。整数除法运算的结果有两部分,一部分是商,会被存放在寄存器eax中,如图1.25第56行所示;另一部分是余数,会被存放在寄存器edx中。当我们进行”c = a%b;”的运算时,就需要用到除法运算的余数,如图1.25第61至64行所示。第57行的inc指令是increment的缩写,用于对操作数进行加1操作。第65至67行对应的是”c = -a;”,其中第66行的指令neg是negative的缩写,数学上是取相反数的意思,如果有符号数a为-1,即0xFFFFFFFF,经过neg指令的处理后,就得到a的相反数1,即0x00000001。而第68至70行对应的是”c = ~a;”,第69行的not指令用于按位取位,如果a 为-1,则按位取反为得到的是0x00000000。

让我们再来看一下C语言中的以下运算符,如图1.26所示。此处,我们的目的是熟悉UCC中用到的有条件跳转指令。

0818b9ca8b590ca3270a3433284dd417.png

图1.26   有条件跳转指令

图1.26中第21行先用cmp指令比较变量a是否等于0,若不相等,则跳转到基本块”.BB2”处,如果相等,则执行第24行的指令,而第23行在汇编中只是个标号。而第34行的指令jle是Jump if Less or Equal的缩写。第32至34行的功能是:如果a小于等于b,则跳转到基本块”.BB6”;否则(即a大于b),则执行第36行,作”c++;”的操作。常用的条件跳转指令如下所示:

JE

Jump if Equal

==

JNE

Jump if Not Equal

!=

JG

Jump if Greater

>

有符号数

JA

Jump if Above

>

无符号数

JL

Jump if Less

<

有符号数

JB

Jump if Below

<

无符号数

JGE

Jump if Greater or Equal

>=

有符号数

JAE

Jump if Above or Equal

>=

无符号数

JLE

Jump if Less or Equal

<=

有符号数

JBE

Jump if Below         or Euqal

<=

无符号数

我们以0xFFFFFFFF和0x00000000为例,如果都视为有符号数,则补码0xFFFFFFFF是-1,而0x00000000为0,此时有-1要小于0;但如果视为无符号数,则0xFFFFFFFF要大于0。用于两个整数比较的指令一般都用cmp,指令cmp实际进行的运算是整数的减法,如果要把这两个数的比较当作有符号整数之间的比较,则C编译器在产生cmp指令后,需要选择有符号数的条件跳转指令,如JG, JL,JGE和JLE;而如果要当作无符号整数之间的比较,则C编译器在产生cmp指令后,需要选择JA,JB,JAE和JBE等指令。如图1.27所示,运行该程序,可以发现第9、12和第15行的printf语句会被执行,而第6行的printf语句没有被执行。图1.27第5行,我们进行的是有符号整数之间的比较,此时-1大于0不成立;而第8行进行的是无符号整数之间的比较,此时无符号数0xFFFFFFFF大于0是成立的;我们还注意到,第11行和第14行的条件是成立的,因为进行signed  int和unsigned int之间二元运算时,signed  int会被提升为unsigned  int。

0818b9ca8b590ca3270a3433284dd417.png

图1.27     整数比较

我们很少会写出类似图1.27第11行这样的代码,但是如果一不小心就可能写出下图1.28所示,运行结果竟然是” -1 > 0”。因此,有符号整数与无符号整数还是尽量不要混合在一起处理,其结果可能出乎意料。由图1.28第22行的jbe指令可以发现,此处是把a和b都当作无符号来比较了。需要注意的是,不论是把a当有符号数,还是无符号来比较,a中的值始终都是0xFFFFFFFF。

0818b9ca8b590ca3270a3433284dd417.png

图1.28  有符号和无符号整数

在这一小节中,我们有意地忽略了float和double类型的浮点数。原理上,CPU只要实现了整数的加减乘除功能,就可以在整数运算的基础上,进行浮点数的加减乘除,这被称为浮点运算的软件实现。软件实现浮点运算的优点是可减少硬件成本,在单片机等对成本很敏感的场合,经常能看到由软件实现的浮点数加减乘除运算。而在稍高端的计算机系统中,浮点运算一般都会由专门的浮点运算芯片来实现,例如Intel的x87芯片,就是一个典型的Float Point Unit,缩写为FPU,浮点运算单元。在下一小节中,我们会讨论一下UCC编译器用到的x87浮点运算指令。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值