C中的数组索引超出限制
为什么C
在数组索引超出范围的情况下进行区分
#include <stdio.h> int 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 ++实际上并没有对数组进行任何边界检查。 这取决于操作系统,以确保您正在访问有效的内存。
在这种情况下,你正在声明一个基于栈的数组。 根据特定的实现,访问数组边界之外的内容将访问已经分配的堆栈空间的另一部分(大多数操作系统和线程为堆栈预留一部分内存)。 只要你恰好在预先分配的堆栈空间中玩耍,一切都不会崩溃(注意我没有说工作)。
最后一行发生的事情是,你现在已经访问了分配给栈的内存部分。 因此,您将索引到未分配给进程的内存的一部分,或以只读方式进行分配。 操作系统看到这一点,并向进程发送段错误。
这是C / C ++在边界检查方面如此危险的原因之一。
段错误不是你的C程序的一个预期的行为,它会告诉你索引超出了界限。 相反,这是未定义行为的意外后果。
在C和C ++中,如果你声明一个数组
type name[size];
您只能访问索引从0
到size-1
元素。 除此之外的任何内容都会导致未定义的行为。 如果这个指数接近这个范围,那么很可能你就读了你自己的程序的内存。 如果索引大大超出范围,则很可能您的程序将被操作系统所杀。 但是你不知道,任何事情都可能发生。
为什么C允许这样做? 那么,C和C ++的基本要点就是不要提供function,如果它们性价比高的话。 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]$
如果在看到arrays访问超出界限的情况下将硬盘格式化(这对于它是合法的),则执行的质量将会相当差。 我喜欢在ANSI C Rationale文档中阅读这些内容。
如果您尝试访问您的进程不拥有的内存,通常只会出现分段错误。
在a[11]
(和a[10]
)的情况下,你所看到的是你的进程拥有的内存,但不属于a[]
数组。 a[25000]
距离a[]
很远,可能完全不在你的记忆之中。
更改a[11]
更隐晦,因为它默默地影响了一个不同的variables(或者当你的函数返回时可能导致不同分段错误的栈帧)。
C不是这样做的。 操作系统的虚拟memeory子系统是。
在你只是略微超出限制的情况下,你正在寻址为你的程序分配的memeory(在这种情况下在堆栈调用栈)。 如果你远离界限,你正在处理的内存不是交给你的程序,操作系统正在抛出一个分段错误。
在某些系统上,也有一个操作系统强制执行“可写入”内存的概念,你可能会试图写入你自己的记忆,但被标记为不可写。
只是为了增加别人的意思,在这种情况下你不能依赖程序简单的崩溃,如果你尝试访问超出“数组界限”的内存位置,将不会发生什么情况。 就像你做了这样的事情一样:
int *p; p = 135; *p = 14;
这只是随机的; 这可能工作。 它可能不会。 不要这样做。 代码来防止这些问题。
这不是一个C问题,它是一个操作系统问题。 你的程序已经被授予了一定的内存空间,你在里面做的任何事都没有问题。 只有当您访问您的进程空间之外的内存时才会发生分段错误。
并不是所有的操作系统对每个进程都有独立的地址空间,在这种情况下,您可以在没有任何警告的情况下破坏另一个进程或操作系统的状态。
正如提到的,一些编译器可以在编译时检测到一些超出边界的数组访问。 但是在编译时检查界限并不能抓住一切:
int a[10]; int i = some_complicated_function(); printf("%d\n", a[i]);
为了检测这个,运行时检查必须被使用,并且由于它们的性能影响而在C中被避免。 即使知道编译时的数组大小(即sizeof (a)),也不能在不插入运行时检查的情况下防止这种情况发生。
正如我理解这个问题和意见,你明白为什么当你访问内存越界时会发生坏事,但你想知道为什么你的特定编译器没有警告你。
编译器可以警告你,许多人的警告级别最高。 然而,这个标准是为了让人们能够运行各种设备的编译器,以及具有各种function的编译器而编写的,因此标准要求最less,同时保证人们可以做有用的工作。
有几次标准要求某种编码风格会产生诊断。 有几个标准不需要诊断的时间。 即使需要诊断,我也不知道标准说明了什么地方应该是什么地方。
但是你在这里并不是完全没有感觉。 如果你的编译器不警告你,林特可能。 此外,还有一些工具可以检测堆上arrays的这种问题(运行时),其中一个比较有名的是电栅(或DUMA )。 但是,即使电围栏也不能保证它会抓住所有的超限错误。
C哲学总是相信程序员。 而且也没有检查范围允许C程序运行得更快。