首页 > 技术文章 > 从一个乘法来分析C语言

bwangel23 2015-02-05 11:43 原文

  昨天碰到一个很奇怪的问题,首先来看这段代码:

 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中。所以我们最终得到的结果也是一个溢出的结果。

 

  分析完了之后,发现我这是舍进求远啊,现在也不知怎么了,遇到点啥就喜欢反汇编出来看看。。。

推荐阅读