【问题标题】:does checking for nullptr 100% protect from segfault regarding memory layout?检查 nullptr 100% 是否可以防止有关内存布局的段错误?
【发布时间】:2020-01-07 16:01:41
【问题描述】:

假设我正在迭代一个数组,但超出了这个数组,因为我的循环很愚蠢。
如果内存中的一个对象直接位于与这个数组同类型的这个数组之后,可以
检查array[invalid_index] == nullptr
保护我?

检查索引(大小未知)是否对 C 数组有效的正确方法是什么?

【问题讨论】:

  • 可以检查数组[invalid_index] == nullptr 保护我吗? -- 不能。
  • 不能。无法保证 x 超出范围的 array[x] 甚至是可读的。
  • 您为什么认为nullptr 有帮助? 0% 更接近真相...
  • 没有。检查索引是否对 C 数组有效的正确方法是计算大小或跟踪大小并将索引与该大小进行比较。
  • 只有两种方法可以知道何时停止迭代,知道大小/有一个结束迭代器,或者使用一个标记值。

标签: c++ segmentation-fault nullptr memory-layout


【解决方案1】:

无法通过指向数组第一个元素的指针来确定数组的大小。您需要以某种方式传达尺寸。

常见的策略是将数组保持为数组并通过类型系统传达大小,将大小作为单独的值提供或使用标记值(如 c 字符串中的空字符)。在 c++ 中,建议使用std::vector 或std::array,它们总是知道自己的大小。

试图取消引用超出该数组边界的数组元素是未定义的行为。一旦您尝试阅读array[invalid_index],您就会有未定义的行为。所以不可能将array[invalid_index] 用于任何有用的目的,包括边界检查。 nullptr 在这里根本没有作用。

【讨论】:

    【解决方案2】:

    不,检查nullptr 并不能保护您。完全没有。 0%。

    是您创建了有问题的数组。检查索引是否有效的正确方法是知道您创建的数组的大小(您这样做;记住:创建数组的是 您),然后检查索引是否在范围。更好的是,编写代码时,索引一开始就不会超出范围,因此,一开始就不需要检查……

    除了数组的最后一个元素之后的假设元素之外,仅尝试计算(甚至不访问,只是计算)不属于数组的元素的地址数组立即调用未定义的行为[expr.add]/4。而且,无一例外,尝试访问实际上不是数组元素的元素总是会调用未定义的行为,无论如何。 100%。内置的下标运算符只是计算给定元素的地址并访问该地址[expr.sub]/1 处的对象的简写。因此,您实际上甚至无法在进入未定义行为领域之前将读取的值与nullptr 进行比较……

    【讨论】:

      【解决方案3】:

      这种检查不会保护你。

      许多 C API 与使用它的程序员有某种契约,即他们将遵守 API 指定的约定,而无需对该契约进行任何严格的机械强制执行。例如,基于 C 的字符串 API 大多断言字符串始终以空值结尾,并且任何基于 C 的字符串 API 的正确使用都将假定并依赖于以空值结尾的字符串。

      当本合同生效时,通常认为执行这些检查是安全的,因为向您传递字符串的人应该保证此合同,通常通过填充字符串或具有额外空值的数据结构,专门且明确地用于履行此合同,如下所示:

      char string[13] = "Hello World!"; //string[12] contains the value 0
      
      /*...*/
      char const* str = string;
      
      do {
          char curr = *str++;
          if(curr == 0) 
              break;
          /*...*/
      } while(true);
      

      在大多数基于 C 的字符串 API 中,在保证合同的特定上下文中,该代码被认为是“安全的”。

      但这取决于程序员自己的行为,并且不能保证。对于程序员来说,他们的代码行为不端并不需要太多,就像这样:

      char string[12] = "Hello World"; 
      string[11] = '!';
      

      会立即打破我刚刚展示的字符串循环。这就是为什么 C++ 程序员觉得(在我看来是正确的)这些类型的检查是不安全的:除非您相信您的用户(程序员)遵守您的规则,否则它们根本无法保证正确。

      这就是为什么我们更喜欢不会像这样意外中断的 API。

      std::string string = "Hello World!";
      for(char curr : string) { //for-each loop, intrinsically safe unless the iterators are improperly implemented
          /*Do whatever with curr*/
      }
      

      所以回到你原来的问题:处理这个问题的安全方法是使用专门设计用来保证我们安全的对象。使用std::array<T, N> 而不是T arr[N],这将允许使用for-each 循环。如果您需要使用基于索引的迭代,请在性能不重要的任何情况下首选at(index) 而不是[index]。这些做法将使您的代码安全,而不是仅仅相信您的数据配置正确。

      【讨论】:

        【解决方案4】:

        是否检查 nullptr 100% 防止段错误

        没有。访问无效内存的方法有很多种,空指针只是其中一种。

        你必须做的是避免所有未定义的行为。

        检查索引(大小未知)是否对 C 数组有效的正确方法是什么?

        检查的方法是与数组的大小进行比较:

        if (index < size)
            // valid
        

        如果您不知道数组的大小,则无法检查其有效性。其他典型的错误来源是错误地假定数组的大小或生命周期。

        【讨论】:

          猜你喜欢
          • 2019-07-18
          • 1970-01-01
          • 1970-01-01
          • 2011-02-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-16
          相关资源
          最近更新 更多