【问题标题】:Array index out of bound behavior数组索引越界行为
【发布时间】:2010-10-14 21:01:33
【问题描述】:

为什么 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 arrays


    【解决方案1】:

    问题在于 C/C++ 实际上并没有对数组进行任何边界检查。这取决于操作系统来确保您访问的是有效内存。

    在这种特殊情况下,您要声明一个基于堆栈的数组。根据特定的实现,访问数组边界之外的访问将只是访问已分配堆栈空间的另一部分(大多数操作系统和线程为堆栈保留一定部分的内存)。只要您碰巧在预先分配的堆栈空间中玩耍,一切都不会崩溃(注意我没有说工作)。

    最后一行发生的情况是,您现在访问的内存超出了为堆栈分配的部分内存。因此,您正在索引未分配给您的进程或以只读方式分配的内存部分。操作系统看到这一点并向进程发送段错误。

    这是 C/C++ 在边界检查方面如此危险的原因之一。

    【讨论】:

    • 但是为什么编译器或链接器不给出错误,他们不知道数组大小吗?如果不是,那么 sizeof(a) 如何正常工作?
    • @Kazoom,C 可以知道非常特定的数组访问子集是否合法。但这些远远超过了无法检测到的病例数量。我的猜测是该功能未实现,因为这样做很昂贵,并且仅在部分场景中有用
    • 作为上面的例子,想象一个简单的例子“a[b]=1;” - 数组边界检查必须在运行时完成,这将花费额外的 CPU 周期来处理每个(或大多数)数组操作。
    • @Kazoom,编译器知道 a 的长度是 10,而 int 的单位大小是 4(例如),所以它只是使用值 40。
    • 真正的 问题是 C 和 C++ 实现 通常不检查边界(无论是在编译时还是在运行时)。他们完全被允许这样做。不要为此责备语言。
    【解决方案2】:

    段错误不是您的 C 程序的预期操作,它会告诉您索引超出范围。相反,这是未定义行为的意外结果。

    在 C 和 C++ 中,如果您声明一个数组,如

    type name[size];
    

    您只能访问索引从0size-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 文档中阅读这些内容。

    【讨论】:

    • 我已经删除了我自己的帖子,你更早并且提供了最广泛的答案:)
    • char foo[2][8]; 出现了一个更棘手的问题,因为 C 和 C++ 标准似乎都没有有意从对象类型中排除二维字节数组,这些对象的所有字节都像被访问一样它们是平面字符数组,它们似乎将foo[0][i] 的含义视为将foo 的地址作为char* 并访问索引i 处的元素。但他们也说foo[0][i] 仅对小于 8 的i 值有效。
    【解决方案3】:

    如果您尝试访问不属于您的进程的内存,通常只会出现分段错误。

    您在a[11](顺便说一下a[10])中看到的是您的进程拥有但不属于a[] 数组的内存。 a[25000]a[] 相差甚远,可能完全不在你的记忆中。

    更改a[11] 更加隐蔽,因为它会默默地影响不同的变量(或当您的函数返回时可能导致不同分段错误的堆栈帧)。

    【讨论】:

      【解决方案4】:

      C 没有这样做。操作系统的虚拟内存子系统是。

      如果您只是稍微越界,您正在处理为您的程序分配的内存(在这种情况下位于堆栈调用堆栈上)。在您超出范围的情况下,您正在处理未交给程序的内存并且操作系统抛出分段错误。

      在某些系统上,还有一个操作系统强制执行的“可写”内存概念,您可能正在尝试写入您拥有但被标记为不可写的内存。

      【讨论】:

        【解决方案5】:

        只是补充一下其他人所说的话,在这些情况下,您不能仅仅依靠程序崩溃,如果您尝试访问超出“数组边界”的内存位置,则无法保证会发生什么。就像你做了类似的事情一样:

        int *p;
        p = 135;
        
        *p = 14;
        

        这只是随机的;这可能有效。可能不会。不要这样做。防止此类问题的代码。

        【讨论】:

        • 不一样。取消引用未初始化的指针应假定为随机指针。访问数组末尾之后的一项更有可能不会崩溃,因为系统通常一次分配一整页内存(4KB 或更多),在数组末尾留下一些空间。
        • 是一样的。 C没有给你这样的保证。如果一个系统以这种方式工作,那很好,但那又如何呢?另外,我认为您应该重新阅读我写的内容,因为您完全没有抓住重点。我不知道你为什么这样回答,我很困惑。
        • p = 135 是类型错误,您不能将int 分配给int*
        【解决方案6】:

        正如 litb 所提到的,一些编译器可以在编译时检测到一些越界的数组访问。但是编译时的边界检查并不能捕获所有内容:

        int a[10];
        int i = some_complicated_function();
        printf("%d\n", a[i]);
        

        要检测到这一点,必须使用运行时检查,并且在 C 中避免使用它们是因为它们会影响性能。即使在编译时知道 a 的数组大小,即sizeof(a),如果不插入运行时检查,它也无法防止这种情况发生。

        【讨论】:

          【解决方案7】:

          根据我对问题和 cmets 的理解,您理解为什么当您越界访问内存时可能会发生不好的事情,但您想知道为什么您的特定编译器没有警告您。 p>

          编译器可以向您发出警告,而且许多编译器会以最高警告级别发出警告。然而,该标准的编写是为了允许人们为各种设备运行编译器,以及具有各种特性的编译器,因此该标准要求尽可能少,同时保证人们可以做有用的工作。

          有几次标准要求某种编码风格会产生诊断。还有其他几次标准不需要诊断。即使需要进行诊断,我也不知道标准中有什么地方规定了确切的措辞。

          但你在这里并没有完全被冷落。如果您的编译器没有警告您,Lint 可能会。此外,还有许多工具可以检测堆上阵列的此类问题(在运行时),其中最著名的工具之一是 Electric Fence(或 DUMA)。但是即使是 Electric Fence 也不能保证它会捕获所有的溢出错误。

          【讨论】:

            【解决方案8】:

            这不是 C 问题,而是操作系统问题。您的程序已被授予一定的内存空间,您在其中所做的任何事情都很好。分段错误仅在您访问进程空间之外的内存时发生。

            并非所有操作系统都为每个进程提供单独的地址空间,在这种情况下,您可以在没有警告的情况下破坏另一个进程或操作系统的状态。

            【讨论】:

              【解决方案9】:

              C 哲学始终是信任程序员。而且不检查边界可以让程序运行得更快。

              【讨论】:

                【解决方案10】:

                正如 JaredPar 所说,C/C++ 并不总是执行范围检查。如果您的程序访问已分配数组之外的内存位置,您的程序可能会崩溃,也可能不会因为它正在访问堆栈上的其他变量。

                要回答关于 C 中 sizeof 运算符的问题: 您可以可靠地使用 sizeof(array)/size(array[0]) 来确定数组大小,但使用它并不意味着编译器将执行任何范围检查。

                我的研究表明,C/C++ 开发人员认为您不应该为不使用的东西付费,他们相信程序员知道他们在做什么。 (请参阅已接受的答案:Accessing an array out of bounds gives no error, why?

                如果您可以使用 C++ 而不是 C,也许可以使用向量?您可以在需要性能时使用 vector[](但没有范围检查),或者更优选地使用 vector.at() (以性能为代价进行范围检查)。请注意,vector 已满时不会自动增加容量:为安全起见,请使用 push_back(),它会在必要时自动增加容量。

                更多矢量信息:http://www.cplusplus.com/reference/vector/vector/

                【讨论】:

                  猜你喜欢
                  • 2013-07-27
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多