运算顺序引发的一系列有趣问题

 

     问题:采用递归方法计算给定整型数组元素之和

     以下给出几种递归算法的实现:

 1 int sum1(int a[], int n)
 2 {
 3     if(n > 0)
 4         return a[n-1] + sum1(a, n-1);
 5     else
 6         return 0;
 7 }
 8 
 9 int sum2(int a[], int n)
10 {
11     if(n > 0)
12         return a[n-1] + sum2(a, --n);
13     else
14         return 0;
15 }
16 
17 int sum3(int a[], int n)
18 {
19     if(n > 0)
20         return a[--n] + sum3(a, n-1);
21     else
22         return 0;
23 }
24 
25 int sum4(int a[], int n)
26 {
27     if(n > 0)
28         return a[n] + sum4(a, --n);
29     else
30         return 0;
31 }
32 
33 int sum5(int a[], int n)
34 {
35     if(n > 0)
36         return a[--n] + sum5(a, n);
37     else
38         return 0;
39 }
40 
41 int sum6(int a[], int n)
42 {
43     while(n > 0)
44         return a[n-1] + sum6(a, n-1);
45 
46     return 0;
47 }

     为验证和比较上述几种算法实现,编写测试代码如下:

 1 int main(void)
 2 {
 3     int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 4     printf("sum1 = %d\n", sum1(a, 10));
 5     printf("sum2 = %d\n", sum2(a, 10));
 6     printf("sum3 = %d\n", sum3(a, 10));
 7     printf("sum4 = %d\n", sum4(a, 10));
 8     printf("sum5 = %d\n", sum5(a, 10));
 9     printf("sum6 = %d\n", sum6(a, 10));
10     return 0;
11 }

     在GCC3.2.3编译器下执行结果为:

1 sum1 = 55
2 sum2 = 55
3 sum3 = 30
4 sum4 = -1076625666(随机值)
5 sum5 = 55
6 sum6 = 55

     而在VC6.0编译器下执行结果为:

1 sum1 = 55
2 sum2 = -858993415(固定值)
3 sum3 = 30
4 sum4 = 55
5 sum5 = 55
6 sum6 = 55

     可见:

     1. 对于a[n-1] + sum2(a, --n),GCC编译器先加法后将n自减,等效于a[n-1] + sum2(a, n-1);而VC编译器先将n自减,等效于a[n-2] + sum2(a, n-1);

     2. 对于a[--n] + sum3(a, n-1),GCC编译器先将n自减,等效于a[n-1] + sum2(a, n-2);而VC编译器与之相同;

     3. 不同的编译器对表达式和自减(或自增)运算符的处理顺序不同。因此,编程时算式中应采用直白易懂的写法(如sum1),避免使用自增/自减;

     4. 递归算法中,sum1中的if与sum6中的while等效。

     5. 遇到语法正确但运行诡异的问题时,不妨怀疑编译器是否存在bug。

     最后,简要提及两种编译器下sum4和sum2执行后的异常值。分析函数调用过程可知,两个异常值均由数组越界导致(gcc-sum4的a[10],vc-sum2的a[-1])。-1076625666的16进制表示形式为0xbfd3fefe,很明显是个Linux栈区地址;而-858993415的16进制表示形式为0xccccccf9,0xcc是VC编译器Debug模式下系统为未初始化的栈区变量所赋的初值。此外,因栈区地址为运行时概念,故每次执行后sum4结果可能略有变化;而VC的0xcc特殊占位符取值固定,故sum2结果不随执行次数而变。

     将测试代码稍加改造:

 1 int main(void)
 2 {
 3     int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
 4     int n = sizeof(a) / sizeof(a[0]);
 5     printf("sum1 = %d\n", sum1(a, n));
 6     printf("sum2 = %d\n", sum2(a, n));
 7     printf("sum3 = %d\n", sum3(a, n));
 8     printf("sum4 = %d\n", sum4(a, n));
 9     printf("sum5 = %d\n", sum5(a, n));
10     printf("sum6 = %d\n", sum6(a, n));
11     return 0;
12 }

     则GCC编译器会给出不同的结果:

1 sum1 = 55
2 sum2 = 55
3 sum3 = 30
4 sum4 = 64
5 sum5 = 55
6 sum6 = 55

 

     因时间有限,本文仅就表面现象说明,未进行深入分析。读者若有兴趣可自行分析下背后的机理,分析手段参考如下文章:

 

posted @ 2014-06-11 19:30  clover_toeic  阅读(668)  评论(0编辑  收藏  举报