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

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

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


 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

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

运算顺序引发的一系列有趣问题,布布扣,bubuko.com

时间: 2024-10-25 03:51:26

运算顺序引发的一系列有趣问题的相关文章

java中+(加号或字符串连接)运算顺序的问题

由于java中+(加号或字符串连接)的运算顺序是从左向右的,所以一下程序的输出会得到两个不同的结果 1 public class Test { 2 public static void main(String[] args) { 3 System.out.println("ddd"+7+5); 4 System.out.println(7+5+"ddd"); 5 6 } 7 } 结果为ddd75 12ddd

值得深思的连续赋值--赋值运算符运算顺序

最近在看到以前前辈留下的文章,对于连续赋值( var a={n:1}; a.x=a={n:2}) )这个知识点,一开始也搞不清楚,但是最终还是说服了自己,谈谈自己的心得.以下代码能回答正确的可以忽略本文. 1 var a={n:1}; 2 var b=a; 3 a.x = a = {n: 2}; 4 console.log(a.x); //? 5 console.log(b.x);//? 正确答案是: a.x= undefined; b.x= {n: 2}; 疑惑:为什么a.x与b.x不相等呢?

在centos服务器上配置gitlab钩子引发的一系列问题

为了给公司的服务器上搭建gitlab环境并且配置钩子(实现在本地git push之后服务器自动git pull),整了好久,最后终于把问题解决了,下面是记录安装gitlab之后引发的一系列问题: 首先搭建gitlab是参考了http://www.linuxidc.com/Linux/2016-06/131992.htm这篇文章,使用的是bitnami制作的一键安装包,下载下来的是run格式的文件,需要先给这个文件执行权限: chmod +x filename filename是这个文件的名字 安

java 运算顺序

java 运算顺序,布布扣,bubuko.com

装hadoop引发的一系列事件

为了装hadoop,先要装hadoop.然后用wubi装ubuntu 10.10,接着开始装hadoop,但是装hadoop要先装ssh等一系列软件,用命令apt-get install openssh-server发现没有相应软件,接着更新源,即使网上说的速度最快的源163也无效,然后查了下好像10.10不是LTS版,不再支持.那么就装高点版本的吧,但是高点版本(12.10,14.0等)的下载完成后,点击wubi安装,总是要在线下载,估计要1,2个小时.于是就刻盘吧,光盘?还是U盘吧,接着下载

[原创]ios单元测试引发的一系列研究(一)

最近在研究单元测试,希望引入一个好用的单元测试工具,提高开发团队的产出质量. 不过我本人对单元测试没有什么知识,所以,笼统的从ios上的测试开始研究. 现在想想,本文的主题monkeytalk好像与单元测试木有关系.orz 目前可用的测试框架虽然没有其他开发平台的那样自成体系,也还算是百花齐放. ------------------------------------------------------- 老大自然是OCUnit+XCTool和GHUnit+OCMock.这两种都是需要自己写te

C语言中指针和自增运算符结合时的运算顺序问题

在C语言中,当指针运算符和++或者–结合时很容易分不清运算顺序,在这里总结一下,下面一共分析6中组合: * p++,(* p)++,* (p++),++* p,++( * p), * (++p). 先看段代码以及输出: #include<stdio.h> int main() { int a[3]={1,3,5}; int *p=a; printf("----------------1----------------\n"); printf("%d\n"

js 关于运算顺序的问题

先介绍涉及到的概念 表达式:js中的短语,解释器在遇到表达式时,会将短语进行计算,然后再参与到运算中,表达式即js中的短语. j所有的s代码都是由操作符和表达式组成的,因此除了操作符,其他的都是js中的表达式. 如1 + 1 这段代码中,+是操作符,前面的1和后面的1都是表达式. 表达式的分类: 原始表达式:常量(如π).变量.直接量(数字.字符串.正则表达式).关键字 初始化表达式:初始化对象和初始化数组 函数定义表达式: 函数调用表达式: 属性访问表达式: 对象创建表达式:使用new操作符创

js运算顺序随笔

看代码看到一个js多运算符运算.自己写代码试了一下明白了. 自己示范的代码 运算顺序,先是非用算a的非为false,然后进行严格等于运算.b===false为false.再进行逻辑或运算两个都是falses所以不会进入if语句,不会弹框 原文地址:https://www.cnblogs.com/w7589/p/8274835.html