我最近开始学习C语言,我正在上一门以C为主题的课程。我目前正在玩循环,我遇到了一些奇怪的行为,我不知道如何解释。

#include <stdio.h>

int main()
{
  int array[10],i;

  for (i = 0; i <=10 ; i++)
  {
    array[i]=0; /*code should never terminate*/
    printf("test \n");

  }
  printf("%d \n", sizeof(array)/sizeof(int));
  return 0;
}

在我运行Ubuntu 14.04的笔记本电脑上,这段代码没有崩溃。它运行到完成。在我学校运行CentOS 6.6的电脑上,它也运行得很好。在Windows 8.1上,循环永远不会终止。

更奇怪的是,当我将for循环的条件编辑为:I <= 11时,代码只在运行Ubuntu的笔记本电脑上终止。它永远不会在CentOS和Windows中终止。

有人能解释一下内存中发生了什么吗?为什么运行相同代码的不同操作系统会产生不同的结果?

编辑:我知道for循环越界了。我是故意这么做的。我只是不明白在不同的操作系统和计算机上,这种行为是如何不同的。


当前回答

在我运行Ubuntu 14.04的笔记本电脑上,这段代码不会中断它的运行直至完成。在我学校运行CentOS 6.6的电脑上,它也运行得很好。在Windows 8.1上,循环永远不会终止。 更奇怪的是,当我将for循环的条件编辑为:I <= 11时,代码只在运行Ubuntu的笔记本电脑上终止。CentOS和Windows永远不会终止。

您刚刚发现了内存踩踏。你可以在这里读到更多关于它的信息:什么是“memory stomp”?

当你分配int数组[10],i;,这些变量进入内存(具体地说,它们分配在堆栈上,这是一个与函数相关的内存块)。数组[]和I在内存中可能是相邻的。似乎在Windows 8.1中,i位于数组[10]中。在CentOS上,i位于数组[11]中。在Ubuntu上,它不在这两个位置(也许它在数组[-1]?)

尝试将这些调试语句添加到代码中。你应该注意到在迭代10或11时,数组[i]指向i。

#include <stdio.h>
 
int main() 
{ 
  int array[10],i; 
 
  printf ("array: %p, &i: %p\n", array, &i); 
  printf ("i is offset %d from array\n", &i - array);

  for (i = 0; i <=11 ; i++) 
  { 
    printf ("%d: Writing 0 to address %p\n", i, &array[i]); 
    array[i]=0; /*code should never terminate*/ 
  } 
  return 0; 
} 

其他回答

当你遍历i==9时,你把0赋给了实际上位于数组后面的“数组项”,所以你覆盖了一些其他数据。最可能的情况是,你重写了i变量,它位于a[]之后。这样,您只需将i变量重置为零,从而重新启动循环。

如果你在循环中输出i,你自己就会发现:

      printf("test i=%d\n", i);

而不仅仅是

      printf("test \n");

当然,这个结果很大程度上取决于变量的内存分配,而这又取决于编译器及其设置,所以它通常是Undefined Behavior——这就是为什么在不同的机器或不同的操作系统或不同的编译器上的结果可能不同。

你有一个边界违反,在非终止平台上,我相信你无意中在循环结束时将I设置为0,这样它就会重新开始。

数组[10]无效;它包含10个元素,从数组[0]到数组[9],数组[10]是第11个。你的循环应该在10之前停止,如下所示:

for (i = 0; i < 10; i++)

数组[10]落在实现定义的位置,有趣的是,在两个平台上,它落在i上,这些平台显然是直接放在数组后面。I被设置为0,循环将永远继续下去。对于其他平台,i可能位于array之前,或者array可能在它之后有一些填充。

它在数组[10]上是未定义的,并给出了之前描述的未定义行为。你可以这样想:

我的购物车里有10样东西。它们是:

0:一盒麦片 1:面包 2:牛奶 3:派 4:鸡蛋 5:蛋糕 6:一杯2升的苏打水 7:沙拉 8:汉堡 9:冰淇淋

购物车[10]未定义,在某些编译器中可能会出现越界异常。但是,很多人显然没有。显而易见的第11件商品实际上并不在购物车中。第11项是指,我称之为“恶作剧项目”。它从未存在过,但它就在那里。

为什么一些编译器给i一个数组[10]或数组[11]甚至数组[-1]的索引是因为你的初始化/声明语句。一些编译器将其解释为:

“为数组[10]分配10个int块和另一个int块。为了方便起见,把它们放在一起。” 和之前一样,但是移动一两个空格,这样数组[10]就不指向i了。 执行与前面相同的操作,但是在数组[-1]处分配i(因为数组的索引不能或不应该为负),或者在完全不同的位置分配i,因为操作系统可以处理它,这样更安全。

一些编译器希望运行得更快,而一些编译器更喜欢安全。这一切都与环境有关。例如,如果我正在为古老的BREW操作系统(基本手机的操作系统)开发一款应用程序,它就不会关心安全性。如果我开发的是iPhone 6,那么它无论如何都能运行得很快,所以我需要强调安全性。(说真的,你读过苹果的应用商店指南吗,或者读过Swift和Swift 2.0的开发吗?)

我将建议一些我在上面没有发现的东西:

赋值数组[i] = 20;

我想这应该会终止所有的代码..(如果你保持i< =10或ll)

如果运行此程序,您可以确定这里指定的答案已经是正确的[与内存踩脚有关的答案为ex。]

与Java不同,C不做数组边界检查,也就是说,没有ArrayIndexOutOfBoundsException异常,确保数组索引有效的工作留给了程序员。故意这样做会导致未定义的行为,任何事情都可能发生。


对于数组:

int array[10]

索引只在0到9的范围内有效。然而,你正试图:

for (i = 0; i <=10 ; i++)

访问数组[10],将条件更改为I < 10