访问数组超出界限不会报错,为什么?

17 浏览
0 Comments

访问数组超出界限不会报错,为什么?

我在C++程序中超出边界分配值,使用以下代码:

#include 
using 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不检查数组边界,我怎么确定我的程序是否正确,因为这可能导致一些严重的问题?

我用以下代码替换了上面的代码:

vector vint(2);
vint[0] = 0;
vint[1] = 1;
vint[2] = 2;
vint[5] = 5;
cout << vint[2] << endl;
cout << vint[5] << endl;

这个代码也没有出错。

0
0 Comments

访问数组越界不会报错,为什么?

在使用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是否也有类似的选项呢?

0
0 Comments

在C++中,访问数组越界不会引发错误,而是可能导致覆盖一些未使用的栈部分。这是因为g++编译器不会检查数组边界,并且你可能会用3和4来覆盖一些不太重要的内容。但如果你使用更大的数字,则可能会导致程序崩溃。

实际上,如果你继续访问超出栈分配空间末尾的部分,最终程序会崩溃。

然而,你无法通过自身手段来解决这个问题,也许静态代码分析工具可以揭示这些错误,但这只是一种简单的情况,实际上你可能会面临更复杂的错误,即使是对于静态分析工具而言也很难检测出来。

所以,访问数组越界不会产生错误是因为编译器没有对数组边界进行检查,解决方法是使用静态代码分析工具来检测这类错误。

0
0 Comments

在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],结果我所有的色情片都被发送到我 的邮箱了。

0