【问题标题】:Why compiler does not complain about accessing elements beyond the bounds of a dynamic array? [duplicate]为什么编译器不会抱怨访问超出动态数组边界的元素? [复制]
【发布时间】:2011-10-10 01:24:15
【问题描述】:

我正在定义一个大小为 9 的数组。但是当我访问数组索引 10 时,它没有给出任何错误。

int main() {
   bool* isSeedPos = new bool[9];
   isSeedPos[10] = true;
}

我预计会出现编译器错误,因为我的数组中没有数组元素 isSeedPos[10]

为什么我没有收到错误消息?

【问题讨论】:

  • 你能改进一下这个问题的标题吗?它并没有很好地描述这个问题。试着写一个标题,让你的问题与众不同。

标签: c++


【解决方案1】:

这不是问题。

C++ 数组中没有边界检查。您可以访问超出数组限制的元素(但这通常会导致错误)。

如果您想使用数组,您必须自己检查是否超出范围(您可以将 sizee 保存在单独的变量中,就像您所做的那样)。

当然,更好的解决方案是使用标准库容器,例如std::vector。 使用std::vector,您可以

  • 使用myVector.at(i)方法获取第i个元素(如果超出范围会抛出异常)
  • 使用 myVector[i] 与 C 样式数组的语法相同,但您必须自己进行边界检查(例如,在访问之前尝试 if (i < myVector.size()) ...

还请注意,在您的情况下,std::vector<bool> 是一个专门的版本,因此每个bool 只占用一位内存(因此它使用的内存少于bool 的数组,这可能是也可能不是你想要什么)。

【讨论】:

  • 值得一提的是,要使用std::vector 进行边界检查,您不能使用operator[];您需要使用at() 函数。
  • 并且std::命名空间与STL无关!
  • @Martinho Fernandes:已更正 -- @Tomalak Geret'kal:vector 不是 STL 容器和命名空间 std:: 中的吗?
  • @Louen:如果你使用“STL”这个名称作为“标准容器、算法和迭代器库”的简写,有些人会抱怨,坚持认为它只能用来指代那些它们基于。
  • @Tomalak :好的,更正了:)
【解决方案2】:

使用 std::vector 代替。一些实现会在调试模式下进行边界检查。

【讨论】:

    【解决方案3】:

    不,编译器不需要为这种情况发出诊断。编译器不会为您执行边界检查

    您有责任确保不会编写这样的损坏代码,因为编译器不会出错

    【讨论】:

      【解决方案4】:

      与其他语言(如 java 和 python)不同,数组访问在 C 或 C++ 中不受绑定检查。这使得访问数组更快。您有责任确保自己保持在界限内。

      但是,在这种简单的情况下,一些编译器可以在编译时检测到错误。

      此外,一些工具(例如 valgrind)可以帮助您在运行时检测此类错误。

      【讨论】:

        【解决方案5】:

        您使用的是什么编译器/调试器? MSVC++ 会抱怨它并告诉你你写的超出了数组的范围。 但标准并不要求这样做。 它可以随时崩溃,它会导致未定义的行为。

        【讨论】:

        • 这是一个动态分配的数组。它会抱怨吗?
        • 通常是这样。使用 MSVC,它会崩溃回调试器。或者程序退出时产生随机崩溃。但是 MSVC 在检测这些东西方面做得很好。
        • 这听起来不像是 MSVC 在抱怨。听起来程序崩溃是因为你很幸运。
        • @Martinho:他们使用金丝雀数组。调试器可以捕获在一定容差范围内的越界访问。
        【解决方案6】:

        原始数组不进行边界检查。如果你想要边界检查,你应该使用 std::vector 代替。您在数组结束后访问无效内存,纯粹是运气好。

        【讨论】:

          【解决方案7】:

          没有对您提供的索引进行运行时检查,访问元素 10 不正确但可能。可能会发生两件事:

          • 如果您“不走运”,这不会崩溃,并且会返回位于您的数组之后的一些数据。
          • 如果你“幸运”,数组后面的数据不是你的程序分配的,所以禁止访问请求的地址。这将被操作系统检测到并产生“分段错误”。

          【讨论】:

          • “将返回一些位于您的数组之后的数据”或其他任何地方。仅仅因为你运气不好并且你的程序没有崩溃并不会突然给你的程序灌输定义明确的语义。
          • 对。这样的代码在写出数组时可能会产生未定义的行为,您可能会遇到难以跟踪的错误。
          • 当然可以,否则你的鼻腔可能会形成一个黑洞。
          【解决方案8】:

          没有规定在c中检查内存访问的规则,简单明了。当您要求一个布尔数组时,操作系统可能会更快地为您提供 16 位 og 32 位数组,而不是 9 位数组。这意味着您甚至可能不会在别人的空间中写作或阅读。

          C++ 速度很快,而且速度很快的原因之一是因为很少检查你在做什么,如果你要求一些内存,那么编程语言会假设你知道你在做什么,如果操作系统不抱怨,那么一切都会运行。

          【讨论】:

            【解决方案9】:

            没问题!你只是在访问你不应该访问的内存。你可以在数组之后访问内存。

            【讨论】:

            • 你不知道它是“数组后的内存”。您正在调用未定义的行为。 没有没有定义的语义。
            【解决方案10】:

            isSeedPos 不知道数组有多大。它只是一个指向内存位置的指针。当您指向 isSeepPos[10] 时,行为未定义。这迟早会导致段错误,但不需要崩溃,当然也没有标准的错误检查。

            【讨论】:

              【解决方案11】:

              写到那个位置是危险的。

              但是编译器会让你这样做 - 实际上你正在写一个过去分配给该数组的内存的最后一个字节 = 不是一件好事。

              C++ 不像许多其他语言 - 它假定您知道自己在做什么!

              【讨论】:

                【解决方案12】:

                C 和 C++ 都允许您写入任意内存区域。这是因为它们最初源自(并且仍然用于)低级编程,您可能合法地想要写入内存映射外设或类似的,并且因为当程序员已经知道值时省略边界检查会更有效将在范围内(例如,对于数组上的循环 0 到 N,他/她知道 0 和 N 在范围内,因此检查每个中间值是多余的)。

                然而,事实上,如今你很少愿意这样做。如果您使用 arr[i] 语法,您基本上总是希望写入 arr 中声明的数组,而从不做任何其他事情。但如果你愿意,你仍然可以。

                如果您确实写入任意内存(就像您在本例中所做的那样),那么它将成为您程序的一部分,并且它会在您不知道的情况下更改一些其他关键数据(现在,或者稍后当您更改代码并忘记您在做什么时);否则它将写入未分配给您的程序的内存,操作系统将关闭它以防止出现更严重的问题。

                现在:

                • 如果你犯了这样一个明显的错误,许多编译器都会发现它
                • 有一些工具可以测试您的程序是否写入未分配的内存
                • 您可以而且应该使用 std::vector 代替,它在 99% 的时间都需要边界检查。 (检查您是使用 at() 还是 [] 来访问它)

                【讨论】:

                • 哪些编译器会发现这一点?顺便说一句,C++ 不允许你写入任意内存。
                • 谢谢。我可能在编译器检查时弄错了,我实际上并不确定。您是什么意思,C++ 不允许您写入任意内存?您的意思是,如果您在标准受保护的操作系统上运行您的程序,它会以何种语言写入虚拟内存?或者您不能将指针分配给您想要的任何内容并取消引用它?或者写地址 0 真的很麻烦?还是我愚蠢地错过了什么?
                • 我的意思是,即使您编写的代码看起来确实如此,或者编译器实际上可能会编译它,但 C++ 要求您始终写入明确定义的位置。尝试在内存中的任何其他位置写入数据是未定义的行为。
                • 哦,我明白了。对不起。嗯。 headache 使用 char 访问未初始化的内存是不是有一些例外?或者在 C++ 中编写设备驱动程序是不可能的(即使大多数编译器在写入内存位置时确实做了明显的事情)?
                • 如果你一个设备映射到内存,你可以写入已经映射的地址,没问题。但是,如果您声明一个大小为 3 的数组,则不能写入该数组的索引 3。要访问这种内存,您需要一个指向它的指针,或者一个具有其确切大小的数组。你不能用更小的数组“作弊”。
                【解决方案13】:

                这不是 Java。在 C 或 C++ 中没有边界检查;你能写到那个索引是纯粹的运气。

                【讨论】:

                • 他的程序编译出来跟运气一点关系都没有。如果它“成功”运行,我会说这是不幸
                • @Tomalak,嘿,不公平,问题已经改变了——自从我回答它以来,问题发生了很大变化——没有迹象表明 OP 在编译时或运行时是否期望某些东西——所以我假设运行时——即使在 Java 中,这也是运行时异常(AFAIK)。虽然一般观点仍然有效 - 并且有趣地在其他答案中重复多次......
                • 这个问题的许多答案在小方面都具有误导性。
                猜你喜欢
                • 2012-09-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2016-04-22
                • 1970-01-01
                • 2019-06-16
                • 2012-03-28
                • 2016-11-01
                相关资源
                最近更新 更多