访问数组超出界限不会报错,为什么?
访问数组超出界限不会报错,为什么?
我在C++程序中超出边界分配值,使用以下代码:
#includeusing namespace std; int main() { int array[2]; array[0] = 1; array[1] = 2; array[3] = 3; array[4] = 4; cout << array[3] << endl; cout << array[4] << endl; return 0; }
该程序打印出3和4。这是不应该发生的。我使用的是g++ 4.3.3。
以下是编译和运行的命令:
$ g++ -W -Wall errorRange.cpp -o errorRange $ ./errorRange 3 4
只有当分配`array[3000]=3000`时,程序才会出现段错误。
如果gcc不检查数组边界,我怎么确定我的程序是否正确,因为这可能导致一些严重的问题?
我用以下代码替换了上面的代码:
vectorvint(2); vint[0] = 0; vint[1] = 1; vint[2] = 2; vint[5] = 5; cout << vint[2] << endl; cout << vint[5] << endl;
这个代码也没有出错。
访问数组越界不会报错,为什么?
在使用g++编译器时,可以添加命令行选项-fstack-protector-all
来检测越界访问问题。在示例中,运行结果如下:
> g++ -o t -fstack-protector-all t.cc > ./t 3 4 /bin/bash: line 1: 15450 Segmentation fault ./t
这并不能帮助你找到或解决问题,但至少会通过段错误提示你有“某些东西”出了问题。
我刚刚发现了更好的选项:-fmudflap
Angel:现代版的等效选项是-fsanitize=address
,它可以在编译时(如果进行了优化)和运行时都捕获到这个错误。
+1,现在我甚至使用-fsanitize=undefined,address
。但值得注意的是,在标准库中有一些罕见的边界情况,使用sanitizer无法检测到越界访问。因此,我建议额外使用-D_GLIBCXX_DEBUG
选项,该选项会增加更多的检查。
谢谢Hi-Angel。当-fmudflap
和-fsanitize=address
对我无效时,-fsanitize=undefined,address
不仅发现了一个函数没有返回值,还发现了一个越界访问的数组赋值操作。
那么其他编译器如苹果的clang是否也有类似的选项呢?
在C/C++中,当访问数组越界时不会报错,这是因为这属于未定义行为。语言标准中没有明确规定的原因很多。一般来说,当遇到未定义行为时,任何事情都可能发生。应用程序可能会崩溃,可能会冻结,可能会弹出CD-ROM驱动器,或者可能会从你的鼻子中冒出恶魔。它可能会格式化你的硬盘,或者将你的所有色情片发送给你的祖母。
甚至,如果你运气真的不好,它可能会看起来正确工作。
语言只是规定了当你在数组的边界内部访问元素时会发生什么。如果超出边界,会发生什么则没有定义。它可能今天看起来工作正常,但这不是合法的C或C++,并且不能保证下次运行程序时它仍将正常工作。或者它可能已经覆盖了关键数据,而你还没有遇到它会引起的问题。
至于为什么没有边界检查,答案有几个方面:
- 数组是C的剩余物。C数组是最原始的。只是一系列具有连续地址的元素。之所以没有边界检查,是因为它只是暴露了原始内存。在C中,实现一个强大的边界检查机制几乎是不可能的。
- 在C++中,类类型可以进行边界检查。但是数组仍然是旧的兼容C的数组。它不是类。此外,C++还建立在另一个原则上,这使得边界检查不理想。C++的指导原则是"你不需要付出你不使用的代价"。如果你的代码正确,你不需要边界检查,也不应该为运行时边界检查的开销付出代价。
- 因此,C++提供了
std::vector
类模板,它允许两者兼备。operator[]
旨在高效。语言标准并不要求它执行边界检查(尽管也不禁止)。vector还有at()
成员函数,保证执行边界检查。因此,在C++中,如果使用vector,你可以同时获得类似数组的性能和边界检查的能力。
为什么我们使用这个数组的东西很久了,但为什么没有检查这样简单的错误?
C++的设计原则是,它不应该比等效的C代码慢,而C不会进行数组边界检查。C的设计原则基本上是速度,因为它的目标是系统编程。数组边界检查需要时间,因此不会执行。在C++中,对于大多数用途,你应该使用容器而不是数组,并且可以通过使用.at()或[]分别访问元素来选择执行边界检查或不执行边界检查。
这样的检查是有代价的。如果你编写正确的代码,你不想支付那个代价。话虽如此,我已经彻底转变为使用std::vector的at()方法,它是被检查的。使用它已经暴露了我认为是"正确"的代码中的一些错误。
因为C++允许你接近底层编程,并且自动边界检查1)会消耗很多时间,2)会妨碍你应该避免使用的一些巧妙的黑客。这是一种特性,而不是一个bug。如果你想要一个具有边界检查的类似数组的对象,可以创建一个。
因为C++让程序员决定什么是错误。我不知道其他语言是如何进行边界检查的,但如果是在运行时进行的,那么会有相关的开销。C++通常用于应用程序、游戏和操作系统开发,需要速度。
实际上,C++对什么是错误非常明确。越界访问是明确的错误。C++标准只是没有指定写错误代码的后果。
我相信旧版本的GCC在遇到某些类型的未定义行为时实际上会在Emacs中启动一个汉诺塔模拟器。就像我说的,任何事情都可能发生。 😉
已经说了一切,所以这只需要一个小的补充。与发布版本相比,调试版本在这些情况下可能更容易原谅。由于调试二进制文件中包含了调试信息,所以重要的东西被覆盖的可能性较小。这就是为什么调试版本似乎工作正常,而发布版本崩溃的原因。
未定义行为是有害的。我想知道C++委员会能否提供一些补充规定。至少对于调试版本,有时当我们调试代码时,我们不需要性能,但我们确实需要预期的行为。
编译器供应商完全有权为特定的未定义行为定义特定的行为。许多调试版本有边界检查的[]
,这是完全符合规范的。标准不需要关心"调试"和"发布"配置的存在。
这可能导致代码在一台机器上正常工作,在另一台机器上不工作吗?
有一些加固代码的安全功能可以导致更多预期的行为。但这是以速度和资源为代价的。这在加固基于源代码的Linux发行版中使用。当然,它们依赖于编译器。你不应该依赖这些功能,因为它们可以被规避。我希望在调试时它们常常默认开启。
我刚刚尝试访问长度为3的原生数组arr[3],结果我所有的色情片都被发送到我 的邮箱了。