我最近开始学习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循环超出范围。我是故意这样做的。我无法弄清楚不同操作系统和计算机之间的行为有何不同。答案 0 :(得分:356)
在运行Ubuntu 14.04的笔记本电脑上,此代码不会破坏它运行完成。在我学校的运行CentOS 6.6的计算机上,它运行良好。在Windows 8.1上,循环永远不会终止。
更奇怪的是,当我将
for
循环的条件编辑为:i <= 11
时,代码仅在运行Ubuntu的笔记本电脑上终止。 CentOS和Windows永远不会终止。
你刚刚发现内存踩踏。您可以在此处详细了解:What is a “memory stomp”?
当你分配int array[10],i;
时,这些变量会进入内存(具体来说,它们是在堆栈上分配的,这是与该函数关联的内存块)。 array[]
和i
可能在内存中彼此相邻。似乎在Windows 8.1上,i
位于array[10]
。在CentOS上,i
位于array[11]
。在Ubuntu上,它既不在任何地方(可能是在array[-1]
?)。
尝试将这些调试语句添加到您的代码中。您应该注意到,在迭代10或11上,array[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;
}
答案 1 :(得分:98)
这些代码之间存在错误:
int array[10],i;
for (i = 0; i <=10 ; i++)
array[i]=0;
由于array
只有10个元素,因此在最后一次迭代中array[10] = 0;
是缓冲区溢出。缓冲区溢出 UNDEFINED BEHAVIOR ,这意味着它们可能会格式化您的硬盘驱动器或导致恶魔飞出您的鼻子。
所有堆栈变量彼此相邻布局是相当常见的。如果i
位于array[10]
写入的位置,则UB会将i
重置为0
,从而导致无终止循环。
要修复,请将循环条件更改为i < 10
。
答案 2 :(得分:25)
与Java不同,C不进行数组边界检查,即没有ArrayIndexOutOfBoundsException
,确保数组索引有效的工作留给程序员。故意这样做会导致未定义的行为,任何事情都可能发生。
对于数组:
int array[10]
索引仅在0
到9
范围内有效。但是,您正试图:
for (i = 0; i <=10 ; i++)
在此处访问array[10]
,将条件更改为i < 10
答案 3 :(得分:19)
你有违反边界的行为,在非终止平台上,我相信你在循环结束时无意中将i
设置为零,以便重新开始。
array[10]
无效;它包含10个元素,array[0]
到array[9]
,array[10]
是第11个元素。应该编写循环以在 10
之前停止,如下所示:
for (i = 0; i < 10; i++)
array[10]
土地是实施定义的,有趣的是,在你的两个平台上,它落在i
上,这些平台显然是在array
之后直接布局的。 i
设置为零,循环继续。对于您的其他平台,i
可能位于array
之前,或array
之后可能会有一些填充。
答案 4 :(得分:12)
您声明int array[10]
表示array
的索引0
为9
(它可以容纳的10
个整数元素)。但是以下循环,
for (i = 0; i <=10 ; i++)
将0
循环到10
意味着11
次。因此,当i = 10
它将溢出缓冲区并导致Undefined Behavior。
所以试试这个:
for (i = 0; i < 10 ; i++)
,或者
for (i = 0; i <= 9 ; i++)
答案 5 :(得分:7)
在array[10]
未定义,并提供未定义的行为,如前所述。想想这样:
我的杂货车里有10件物品。他们是:
0:一盒麦片
1:面包
2:牛奶
3:馅饼
4:鸡蛋
5:蛋糕
6:2升苏打水
7:沙拉
8:汉堡包
9:冰淇淋
cart[10]
未定义,并且可能在某些编译器中提供超出范围的异常。但是,很多人显然不是。明显的第11个项目是实际上在购物车中的项目。第11个项目指向,我打算打电话,&#34; poltergeist项目。&#34;它从来没有存在过,但它就在那里。
为什么某些编译器为i
或array[10]
或array[11]
提供array[-1]
索引是因为您的初始化/声明语句。一些编译器将其解释为:
int
和另一个array[10]
块分配10个int
s块。 让它更容易,将它们放在一起。&#34; array[10]
不指向i
。i
分配array[-1]
(因为数组的索引不能,或者不应该为负数),或者在以下分配一个完全不同的地方,因为操作系统可以处理它,而且更安全。 有些编译器希望事情变得更快,有些编译器更喜欢安全性。这完全取决于背景。例如,如果我正在为古老的BREW OS(基本手机的操作系统)开发应用程序,它就不会关心安全性。如果我正在为iPhone 6开发,那么它无论如何都可以快速运行,所以我需要强调安全性。 (说真的,您是否已阅读Apple的App Store指南,或阅读Swift和Swift 2.0的开发?)
答案 6 :(得分:6)
由于您创建了一个大小为10的数组,因此for循环条件应如下所示:
int array[10],i;
for (i = 0; i <10 ; i++)
{
目前,您正尝试使用array[10]
从内存中访问未分配的位置,这会导致未定义的行为。未定义的行为意味着您的程序将以不确定的方式运行,因此它可以在每次执行时提供不同的输出。
答案 7 :(得分:5)
好吧,C编译器传统上不检查边界。如果您引用了不属于您的流程的位置,则可能会出现细分错误。但是,局部变量是在堆栈上分配的,并且根据内存的分配方式,数组之外的区域(array[10]
)可能属于进程的内存段。因此,不会抛出分段故障陷阱,这就是您似乎遇到的情况。正如其他人所指出的那样,这是C中未定义的行为,您的代码可能被认为是不稳定的。由于您正在学习C,因此最好养成检查代码中边界的习惯。
答案 8 :(得分:4)
除了内存可能被布置以便尝试写入a[10]
实际上覆盖i
之外,优化编译器也可能确定无法达到循环测试值i
大于10而没有代码首次访问不存在的数组元素a[10]
。
由于尝试访问该元素将是未定义的行为,因此编译器对该程序在该点之后可能执行的操作没有任何义务。更具体地说,由于编译器没有义务生成代码以在任何可能大于10的情况下检查循环索引,因此它没有义务生成代码来检查循环索引;它可以改为假设<=10
测试总是产生真实。请注意,即使代码读取a[10]
而不是编写它,也是如此。
答案 9 :(得分:3)
当您迭代i==9
时,您将零值分配给&#39;数组项目&#39;它实际上位于通过数组之后,因此您将覆盖其他一些数据。很可能是您覆盖位于i
之后的a[]
变量。这样您就可以将i
变量重置为零,从而重新启动循环。
如果您在循环中打印i
,您可能会发现自己:
printf("test i=%d\n", i);
而不仅仅是
printf("test \n");
当然,结果很大程度上取决于变量的内存分配,而后者又取决于编译器及其设置,因此通常未定义行为 - 这就是为什么结果如何不同的机器或不同的操作系统或不同的编译器可能会有所不同。
答案 10 :(得分:0)
错误在部分数组[10]中,w / c也是i的地址(int array [10],i;)。 当array [10]设置为0时,i将为0 w / c重置整个循环和 导致无限循环。 如果array [10]在0-10之间,则会有无限循环。正确的循环应该是(i = 0; i <10; i ++){...} int array [10],i; for(i = 0; i <= 10; i ++) 阵列[I] = 0;
答案 11 :(得分:0)
我会建议我上面找到的东西:
尝试指定数组[i] = 20;
我想这应该在任何地方终止代码..(给你保持i&lt; = 10或ll)
如果这样运行,你可以坚定地确定这里指定的答案已经是正确的[答案与内存踩踏一个有关。]
答案 12 :(得分:-9)
这里有两件事不对。 int i实际上是一个数组元素array [10],如堆栈中所示。因为你允许索引实际上使数组[10] = 0,所以循环索引i永远不会超过10.使它成为for(i=0; i<10; i+=1)
。
i ++,正如K&R所称,“坏风格”。它以i的大小递增i,而不是1. i ++用于指针数学,i + = 1用于代数。虽然这取决于编译器,但它对于可移植性来说并不是一个好的约定。