为什么C
在数组索引超出限制的情况下进行区分
#includeint main() { int a[10]; a[3]=4; a[11]=3;//does not give segmentation fault a[25]=4;//does not give segmentation fault a[20000]=3; //gives segmentation fault return 0; }
据我所知,它正在尝试访问分配给进程或线程的内存,如果是a[11]
或者a[25]
它正在超出堆栈边界a[20000]
.
为什么编译器或链接器没有出错,他们不知道数组大小?如果没有,那么如何sizeof(a)
正常工作?
问题是C/C++实际上没有对数组进行任何边界检查.这取决于操作系统,以确保您访问有效的内存.
在这种特殊情况下,您将声明一个基于堆栈的数组.根据特定的实现,访问数组边界外部只会访问已分配的堆栈空间的另一部分(大多数操作系统和线程为堆栈保留一定的内存部分).只要您恰好在预先分配的堆栈空间中玩游戏,一切都不会崩溃(注意我没说工作).
最后一行发生的事情是,您现在已经访问了超出为堆栈分配的内存部分.因此,您将索引到未分配给进程或以只读方式分配的内存部分.操作系统看到这一点并向进程发送seg错误.
这是C/C++在边界检查方面如此危险的原因之一.
段错误不是C程序的预期操作,它会告诉您索引超出范围.相反,它是未定义行为的意外后果.
在C和C++中,如果声明一个类似的数组
type name[size];
您只能访问索引0
最多的元素size-1
.超出该范围的任何内容都会导致未定义的行为.如果索引接近范围,很可能您会读取自己程序的内存.如果索引大部分超出范围,很可能您的程序将被操作系统杀死.但你无法知道,任何事情都可能发生.
为什么C允许这样做?好吧,C和C++的基本要点是如果性能成本不提供功能.C和C++已经用于高性能关键系统.C已被用作内核和程序的实现语言,其中访问数组边界对于快速访问内存中相邻的对象非常有用.编译器禁止这样做是徒劳的.
为什么不警告呢?好吧,你可以把警告水平提高,并希望编译器的怜悯.这称为实施质量(QoI).如果某些编译器使用开放行为(例如,未定义的行为)来做好事,那么它在这方面具有良好的实现质量.
[js@HOST2 cpp]$ gcc -Wall -O2 main.c main.c: In function 'main': main.c:3: warning: array subscript is above array bounds [js@HOST2 cpp]$
如果它看到你的硬盘格式化,看到数据访问超出范围 - 这对它来说是合法的 - 实现的质量将是相当糟糕的.我很高兴在ANSI C Rationale文档中阅读这些内容.
如果您尝试访问您的进程不拥有的内存,通常只会出现分段错误.
什么你在的情况下看到a[11]
(和a[10]
方式)是你的进程内存并拥有但不属于a[]
阵列.a[25000]
到目前为止a[]
,它可能完全在你的记忆之外.
变化a[11]
更加阴险,因为它会默默地影响另一个变量(或者当函数返回时可能导致不同分段错误的堆栈帧).