我最近开始学习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循环越界了。我是故意这么做的。我只是不明白在不同的操作系统和计算机上,这种行为是如何不同的。


当前回答

这里有两个错误。int i实际上是一个数组元素,数组[10],就像在堆栈上看到的那样。因为你已经允许索引使数组[10]= 0,循环索引i永远不会超过10。令(i=0;我< 10;我+ = 1)。

正如K&R所说,i++是“糟糕的风格”。它增加了i的大小,而不是1。i++是指针数学,I +=1是代数。虽然这取决于编译器,但对于可移植性来说,这不是一个好的约定。

其他回答

因为你创建了一个大小为10的数组,for循环条件应该如下所示:

int array[10],i;

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

目前,您正在尝试使用数组[10]从内存中访问未分配的位置,这导致了未定义的行为。未定义行为意味着您的程序将以不确定的方式运行,因此它可以在每次执行时给出不同的输出。

在我运行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; 
} 

Well, C compiler traditionally does not check for bounds. You can get a segmentation fault in case you refer to a location that does not "belong" to your process. However, the local variables are allocated on stack and depending on the way the memory is allocated, the area just beyond the array (array[10]) may belong to the process' memory segment. Thus, no segmentation fault trap is thrown and that is what you seem to experience. As others have pointed out, this is undefined behavior in C and your code may be considered erratic. Since you are learning C, you are better off getting into the habit of checking for bounds in your code.

你有一个边界违反,在非终止平台上,我相信你无意中在循环结束时将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的开发吗?)