c-数组索引在C中越界
为什么在数组索引超出范围的情况下sizeof(a)会有所区别
#include
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;
}
我了解到,在sizeof(a)或a[25]的情况下,它试图访问分配给进程或线程的内存,而在2963513659726758958914的情况下,它超出堆栈范围。
为什么编译器或链接器没有给出错误,它们是否不知道数组的大小? 如果不是,那么sizeof(a)如何正常工作?
Kazoom asked 2020-07-12T18:01:33Z
10个解决方案
68 votes
问题是C / C ++实际上不对数组进行任何边界检查。 取决于操作系统,以确保您正在访问有效内存。
在这种情况下,您要声明一个基于堆栈的数组。 根据特定的实现方式,在数组范围之外进行访问将仅访问已分配的堆栈空间的另一部分(大多数OS和线程为堆栈保留一部分内存)。 只要您恰好在预先分配的堆栈空间中玩耍,一切都不会崩溃(请注意,我没有说工作)。
最后一行发生的事情是,您现在已经访问了超出分配给堆栈的那部分内存。 结果,您正在索引未分配给进程或以只读方式分配的一部分内存。 操作系统会看到此情况,并将段错误发送给进程。
这是C / C ++在进行边界检查时如此危险的原因之一。
JaredPar answered 2020-07-12T18:01:56Z
21 votes
段错误不是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文档中读到这些内容。
Johannes Schaub - litb answered 2020-07-12T18:02:39Z
6 votes
通常,如果您尝试访问不属于您的进程的内存,通常只会遇到分段错误。
在a[11](顺便说一下,和a[10])的情况下,您看到的是进程确实拥有但不属于a[11]阵列的内存。 a[25000]与a[]距离很远,可能完全在您的内存之外。
更改a[11]更加隐蔽,因为它会默默地影响另一个变量(或堆栈帧,当函数返回时,堆栈帧可能会导致另一个分段错误)。
paxdiablo answered 2020-07-12T18:03:09Z
3 votes
C没有这样做。 操作系统的虚拟内存子系统是。
在您只是稍微超出界限的情况下,您要处理的是为程序分配的内存(在这种情况下,在堆栈调用堆栈中)。 如果您的内存超出界限,那么您将寻址未分配给程序的内存,并且操作系统会引发分段错误。
在某些系统上,还有一个操作系统强制执行的“可写”内存的概念,您可能正在尝试写入自己拥有但被标记为不可写的内存。
dmckee answered 2020-07-12T18:03:38Z
2 votes
仅添加其他人的话,您就不能仅仅依靠程序在这些情况下崩溃就无法保证,如果您尝试访问超出“数组边界”的内存位置,将不会发生任何保证。 就像执行以下操作一样:
int *p;
p = 135;
*p = 14;
那只是随机的; 这可能有效。 可能不会。 不要这样 防止出现此类问题的代码。
BobbyShaftoe answered 2020-07-12T18:04:03Z
2 votes
如litb所述,某些编译器可以在编译时检测到一些超出范围的数组访问。 但是在编译时进行边界检查并不能解决所有问题:
int a[10];
int i = some_complicated_function();
printf("%d\n", a[i]);
为了检测到这一点,必须使用运行时检查,并且由于它们会对性能产生影响,因此在C语言中避免使用它们。 即使知道编译时a的数组大小(即sizeof(a)),也无法在不插入运行时检查的情况下防止这种情况。
Tung Nguyen answered 2020-07-12T18:04:27Z
2 votes
就我理解的问题和评论而言,您了解了为什么超出范围访问内存时会发生不好的事情,但是您想知道为什么您的特定编译器没有警告您。
允许编译器向您发出警告,并且许多警告器会以最高警告级别发出警告。 但是,编写该标准是为了允许人们为各种设备运行编译器,并且编译器具有各种功能,因此,该标准在确保人们可以做有用的工作的同时,对它的要求最少。
该标准几次要求某种编码样式将生成诊断。 在其他几次标准不需要诊断的情况下。 即使需要诊断,我也不知道标准在哪里写出确切的措辞。
但是您并没有完全摆脱困境。 如果您的编译器没有警告您,则可能是Lint。 此外,有许多工具(在运行时)可检测堆上阵列的此类问题,其中最著名的一种是Electric Fence(或DUMA)。 但是,甚至Electric Fence也无法保证会捕获所有超限错误。
Max Lybbert answered 2020-07-12T18:05:02Z
1 votes
那不是C问题,而是操作系统问题。 您的程序已被授予一定的内存空间,您在其中进行的所有操作都可以。 仅当您在进程空间之外访问内存时,才会发生分段错误。
并非所有操作系统的每个进程都有单独的地址空间,在这种情况下,您可以在不发出警告的情况下破坏另一个进程或操作系统的状态。
zimbu668 answered 2020-07-12T18:05:27Z
1 votes
C哲学永远是程序员的信任。 同样,不检查界限也可以使C程序运行得更快。
Jack answered 2020-07-12T18:05:47Z
0 votes
正如JaredPar所说,C / C ++并不总是执行范围检查。 如果程序访问分配的数组之外的内存位置,则程序可能会崩溃,也可能不会崩溃,因为它正在访问堆栈上的某些其他变量。
要回答有关C语言中的sizeof运算符的问题:您可以可靠地使用sizeof(array)/ size(array [0])来确定数组大小,但是使用它并不意味着编译器将执行任何范围检查。
我的研究表明,C / C ++开发人员认为您不应该为自己不使用的东西付钱,他们信任程序员知道自己在做什么。 (请参见已接受的答案:超出范围访问数组不会出错,为什么?)
如果可以使用C ++而不是C,则可以使用vector? 您可以在需要性能时使用vector [](但不进行范围检查),或更优选地,使用vector.at()(以性能为代价进行范围检查)。 请注意,向量在充满时不会自动增加容量:为安全起见,请使用push_back(),必要时会自动增加容量。
有关矢量的更多信息:[http://www.cplusplus.com/reference/vector/vector/]
Jeff answered 2020-07-12T18:06:25Z