从一个乘法来分析C语言
昨天碰到一个很奇怪的问题,首先来看这段代码:
1 #include<stdio.h> 2 int main(int argc,char *argv[]) 3 { 4 long num1 = 203879; 5 long long num2 = 203879; 6 7 long long res1 = num1 * num1; 8 long long res2 = num2 * num2; 9 10 printf("res1 = %lld\n",res1); 11 printf("res2 = %lld\n",res2); 12 13 return 0; 14 }
程序的运行结果如下:
这里感觉很奇怪,203879并没有超过4个字节的范围,但是它的平方超过了,于是我把它的结果存放在一个8字节数中,为什么最终结果还是显示溢出了呢?
然后我又写了一段程序,把它的汇编代码拿出来分析了一下?程序如下:
1 int main(int argc,char *argv[]) 2 { 3 long muln = 203879; 4 long long mulnl = 203879; 5 6 long long num1 = 203879 * 203879; 7 long long num2 = muln * muln; 8 long long num3 = mulnl * mulnl; 9 10 return 0; 11 }
这里我分成三种情况,一种是直接的一个整数当乘数,一个是long型的整数当乘数,还有一个是long long型的整数当作乘数,然后分别计算他们的平方,我用gdb调试的结果如下:
其中前两种情况都溢出了,只有第三种情况正常。然后我们再来查看一下他们的汇编代码,这是我用objdump反汇编出来的汇编代码:
1 int main(int argc,char *argv[]) 2 { 3 8048394: 55 push %ebp 4 8048395: 89 e5 mov %esp,%ebp 5 8048397: 83 e4 f8 and $0xfffffff8,%esp 6 804839a: 83 ec 30 sub $0x30,%esp 7 long muln = 203879; 8 804839d: c7 44 24 0c 67 1c 03 movl $0x31c67,0xc(%esp) 9 80483a4: 00 10 long long mulnl = 203879; 11 80483a5: c7 44 24 10 67 1c 03 movl $0x31c67,0x10(%esp) 12 80483ac: 00 13 80483ad: c7 44 24 14 00 00 00 movl $0x0,0x14(%esp) 14 80483b4: 00 15 16 long long num1 = 203879 * 203879; 17 80483b5: c7 44 24 18 71 b1 90 movl $0xad90b171,0x18(%esp) 18 80483bc: ad 19 80483bd: c7 44 24 1c ff ff ff movl $0xffffffff,0x1c(%esp) 20 80483c4: ff 21 long long num2 = muln * muln; 22 80483c5: 8b 44 24 0c mov 0xc(%esp),%eax 23 80483c9: 0f af 44 24 0c imul 0xc(%esp),%eax 24 80483ce: 89 c2 mov %eax,%edx 25 80483d0: c1 fa 1f sar $0x1f,%edx 26 80483d3: 89 44 24 20 mov %eax,0x20(%esp) 27 80483d7: 89 54 24 24 mov %edx,0x24(%esp) 28 long long num3 = mulnl * mulnl; 29 80483db: 8b 44 24 14 mov 0x14(%esp),%eax 30 80483df: 89 c1 mov %eax,%ecx 31 80483e1: 0f af 4c 24 10 imul 0x10(%esp),%ecx 32 80483e6: 8b 44 24 14 mov 0x14(%esp),%eax 33 80483ea: 0f af 44 24 10 imul 0x10(%esp),%eax 34 80483ef: 01 c1 add %eax,%ecx 35 80483f1: 8b 44 24 10 mov 0x10(%esp),%eax 36 80483f5: f7 64 24 10 mull 0x10(%esp) 37 80483f9: 01 d1 add %edx,%ecx 38 80483fb: 89 ca mov %ecx,%edx 39 80483fd: 89 44 24 28 mov %eax,0x28(%esp) 40 8048401: 89 54 24 2c mov %edx,0x2c(%esp) 41 8048405: 89 44 24 28 mov %eax,0x28(%esp) 42 8048409: 89 54 24 2c mov %edx,0x2c(%esp) 43 44 return 0; 45 804840d: b8 00 00 00 00 mov $0x0,%eax 46 }
首先来看num1的代码(16~20行),203879(31C67H)平方为41566646641(9AD90B171H),编译器直接把这个结果计算了出来,然后取出结果的4个字节,存放到了num1中,然后再用符号位来填充高4字节。
接下来我们再来看num2的代码(21~27行),首先它把203879存放到eax里面,再把相乘的平方结果存放到eax里面,由于eax是32位,所以存放的时候就舍去了高4位,只存放了低4个字节。接下来做的就是判断这个数的符号位是什么,然后再用移位运算得到32个1存放在edx里面,最后再把这个edx的值存放到num2的高四个字节里面。
OK,通过上面这段汇编代码分析,我们再来从C语言的概念上来分析这句代码:
long long num2 = muln * muln ;
首先muln是一个4字节的整数,然后muln * muln得到的结果也是一个四字节的整数(这里产生了溢出),然后再把这个结果转换成8字节的整数,存放到num2中。所以我们最终得到的结果也是一个溢出的结果。
分析完了之后,发现我这是舍进求远啊,现在也不知怎么了,遇到点啥就喜欢反汇编出来看看。。。