学习
实践
活动
专区
工具
TVP
写文章

谁动了我的n值?

问题的引出

在进入今天的正题前,先来看一道题:

下面这个程序,屏幕上会显示几行数组?

5行?emmm……嘿嘿……我们稍后再揭晓。

问题的产生

最近我看到一道c++的编程题:

求12的100次方结果的末三位

当然,题目本身并不难,简单分析如下:

不能傻乎乎的写个for循环从1到100,把12的100次方结果算出来……理论上这样做没问题,但是现实是残酷的,你会把计算机算成死机的。

当然,要达到题目要求,结果还是要计算出来的,只不过我们选择间接计算——题目要求末三位,那咱们就只要每一次的结果的末三位,其余位抛弃好了。

基于上述分析,最后的程序可以这样写:

我们编译运行一下,输入100,结果如下:

那程序结果到底对不对呢?100的结果没法直接验证,那我们输入4、5、6简单验证看一下结果:

用计算器算一下:

12^4=20736

12^5=248832

12^6=2985984

看来结果令人很满意!

然而……

我在写出上述程序之前,是另外一种写法:

唯一不同的是,将每一次结果的后三位通过模取,分别存储到三个数组中,如下:

在Microsoft Visual C++6.0(以下简称VC)中编译执行程序,然而执行结果与我想象中的有所差异:

小南瓜

咦?宝宝明明输入的n是6呀,咋输出变成了2?程序中没有一条语句有修改n的呀!

无奈之下,我这个编程小白求助了檀大佬,大佬看了半天然后给出了最后的结论:

随后,大佬又给出了一个建议:

于是很快,我在DEV C++(以下简称 )中再次编译运行了一遍,结果依旧一样!

随后我又在一个大佬云集的群里求助请教,与此同时,我决定在Visual Studio 2017(以下简称VS)中再试一遍。

我惊愕的发现,是正常的:

此时,群里刘大佬传来反馈:

大佬就是大佬,一针见血的指出了要害……确实是循环里数组访问越界了………………解决了困惑我一晚上的问题!

然而……

重点是:为啥,循环里访问数组越界,n值会被修改?

问题的解决

为了方便调试,我在程序中添加了几行具有显示作用的代码,如下:

输入6,进行调试,结果发现,在循环中访问数组越界,n的值的确会被某种神秘的力量修改!!!!!

细思极恐啊啊啊……

然而……

重点是:此时,我们回到一开始文章开头抛出的问题:下面给出的程序,最终输出在屏幕上的有几行?

经过简单的分析发现,在这个循环中同样存在数组越界的情况,那么它最终会在屏幕上输出几行呢?

通过GIF可以看到,最后输出在屏幕上的是一个无限死循环……纳尼?这是什么操作??

小伙伴

这是为什么呢?

为了说明更加清楚,我们对程序稍作修改如下:

即增加了内存地址的输出,编译运行结果如下:

可以看到,内存地址是循环变化的,我们通过画图来说明问题:

进入循环后,前四次对数组依次赋值为1,这很常规,关键是接下来的分析;

由于一开始定义数组时,定义为:arr[5],即告诉编译器开辟arr[0]、arr[1]、arr[2]、arr[3]、arr[4]这五个空间(整形数组,故一个数组长度为4个字节,所以地址间隔4)。于是乎,在下一个地址号:0019FF3C,程序存放变量i值,如下图:

所以,可以很直观的看到,实际上arr[5]和i指向的是同一个地址空间,于是,接下来执行“arr[5] = 1”,实际上就是等同于执行“i = 1”。由于循环结束的条件是“i

(关于i存放地址为啥子是0019FF3C,可以加一个输出i地址的语句,输出看看就知道了。)

至此,我们明白了,为什么在循环里访问数组越界,很容易造成死循环,原因就是在于,在越界的地方赋值,其实等同于修改循环变量i的值,假如凑巧此时修改的i值满足了跳出循环条件,自然而然就不会出现死循环。

所以,这也解决的我的疑惑,“谁动了我的n值?”,答案是循环赋值越界数组修改的!

那么,根据以上分析,原先我写的那个求12^100的程序,n值应该等于越界赋值的数组的值,即n应该等于change[3]的值,我们运行一下,发现结果的确如此,也证明了我们的分析是正确的!

然而……

嗯mmm,这回是重点了!

可是……

还有一个重点:为啥在VC和DEV中出现了死循环,而在VS中运行,却没有出现修改循环变量的情况呢?

为了简单起见,我们运行文章开头的程序。

经过简单的debug,很快发现端倪,看下图:

本来i值理论上不会超过5,但是此时i被编译器修改成了6,由此可见,VS的编译器不同于VC和DEV的编译器,VS的编译器会自动为越界的数组动态的增加一个内存,但是,很遗憾,这种增加,会引起编译器的运行时异常:

至此,关于循环访问数组越界问题,彻底解决。通过本次研究,我们从中得出一个结论:对于c++的编译器,在编译程序时不会对数组越界产生任何警告或错误,这就对我们程序的执行造成了隐藏的祸患。所以在以后的编程中,尽量做到:

数组定义不越界,凡是使用到数组的地方,第一反应就是检查其是否越界;

最好选择使用动态数组,这样是防止数组越界的最有效的方法。

我猜你还想看

文稿/排版/图片:小南瓜

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20171216G072LB00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

关注

腾讯云开发者公众号
10元无门槛代金券
洞察腾讯核心技术
剖析业界实践案例
腾讯云开发者公众号二维码

扫码关注腾讯云开发者

领取腾讯云代金券