【问题标题】:Is it undefined behavior to form a pointer range from a stack address?从堆栈地址形成指针范围是未定义的行为吗?
【发布时间】:2012-02-25 05:52:39
【问题描述】:

一些 C 或 C++ 程序员甚至会惊讶地发现 storing an invalid pointer is undefined behavior。但是,对于堆或堆栈数组,可以将地址存储在数组末尾之后,这样您就可以存储“结束”位置以供循环使用。

但是从单个堆栈变量形成指针范围是未定义的行为,例如:

char c = 'X';
char* begin = &c;
char* end = begin + 1;

for (; begin != end; ++begin) { /* do something */ }

虽然上面的例子没什么用,但在某些函数需要一个指针范围的情况下,这可能很有用,而你只是有一个简单的值来传递它。

这是未定义的行为吗?

【问题讨论】:

  • 你确定你的措辞是正确的,存储无效指针的地址是未定义的行为吗? int* ptr; int** ptr2 = &ptr 正在存储无效指针的地址。是UB吗?如果你的意思是我们不能让指针指向无效内存,那么我们如何拥有指向NULL的指针?
  • @SethCarnegie: int* ptr; int* ptr2 = &ptr 甚至无法编译,因为ptr2 的类型不匹配。此外,nullptr 是一个特例。
  • @Seth,NULL 是标准保留的特殊值。
  • Channel72 只回答了我的一个问题。 @Mankarse 已修复

标签: c++ c pointers undefined-behavior language-lawyer


【解决方案1】:

这是允许的,行为已定义,beginend 都是安全派生的指针值

在 C++ 标准第 5.7 节 ([expr.add]) 第 4 段中:

就这些运算符而言,指向非数组对象的指针与指向长度为 1 的数组的第一个元素的指针的行为相同,该数组的元素类型为对象的类型。

使用 C 时,可以在 C99/N1256 标准第 6.5.6 节第 7 节中找到类似的子句。

对于这些运算符,指向不是数组元素的对象的指针的行为与指向长度为 1 的数组的第一个元素的指针相同,该数组的类型为元素类型。


顺便说一句,在第 3.7.4.3 ([basic.stc.dynamic.safety]) 节“安全派生的指针”中有一个脚注:

本节不对取消引用指向不是由::operator new 分配的内存的指针施加限制。这保持了许多 C++ 实现使用以其他语言编写的二进制库和组件的能力。这尤其适用于 C 二进制文件,因为取消引用指向由 malloc 分配的内存的指针不受限制。

这表明整个堆栈中的指针运算是实现定义的行为,而不是未定义的行为。

【讨论】:

  • 这里有很多相互矛盾的答案和解释,但这个答案似乎非常明确。
  • 鉴于此问题也被标记为 C,因此在 C99 规范 (N1256) 第 6.5.6 节第 7 节中指定了相同的行为。
  • 谢谢,@tinman。如果您有实际文字,请随时添加引用。
  • [expr.add] 段落似乎只适用于加法运算符。我找不到关系运算符的相应段落。
  • @Mankarse: end = begin + 1... 在我看来,这就像一个加法运算符。
【解决方案2】:

我相信在法律上,您可以将单个对象视为大小为 1 的数组。此外,只要没有取消引用,将指针移到任何数组的末尾之后绝对是合法的。所以我相信它不是UB。

【讨论】:

    【解决方案3】:

    只要您不取消引用 invalid 迭代器,它就不是未定义行为。
    您可以在分配之外持有指向内存的指针,但不允许取消引用它。

    【讨论】:

    • 我链接到的问题表明您不允许持有指向超出分配范围的地址的指针(这让很多人感到惊讶),除非它超出了数组的末尾。
    • 这是哪个。因此在这里定义。
    【解决方案4】:

    ISO14882:2011(e) 的 5.7-5 规定:

    当具有整数类型的表达式被添加或减去时 从一个指针,结果具有指针操作数的类型。如果 指针操作数指向数组对象的一个​​元素,而数组 足够大,结果指向一个元素偏移量 原始元素使得下标的差异 结果和原始数组元素等于积分表达式。 换句话说,如果表达式 P 指向一个 数组对象,表达式 (P)+N(等价于 N+(P))和 (P)-N (其中 N 的值为 n)分别指向 i + n-th 和 i - 数组对象的第 n 个元素,前提是它们存在。此外,如果 表达式 P 指向数组对象的最后一个元素, 表达式 (P)+1 指向数组对象的最后一个元素, 如果表达式 Q 指向数组的最后一个元素 对象,表达式 (Q)-1 指向数组的最后一个元素 目的。如果指针操作数和结果都指向元素 对于相同的数组对象,或数组对象的最后一个元素,评估不应产生溢出;否则,行为是 未定义。

    除非我在那里忽略了某些东西,否则添加仅适用于指向同一数组的指针。对于其他一切,最后一句话适用:“否则,行为未定义”

    编辑: 确实,当您添加 5.7-4 时,事实证明您所做的操作(实际上)是在数组上,因此这句话不适用:

    对于这些运算符,指向非数组对象的指针 行为与指向数组的第一个元素的指针相同 长度为一,对象的类型作为其元素类型。

    【讨论】:

    • 你忽略了一些东西。看我的回答。或者 DeadMG 的。
    【解决方案5】:

    一般来说,指向内存空间之外的行为是未定义的行为,但是“one past the end”有一个例外,根据标准这是有效的。

    因此,在特定示例中,&c+1 是一个有效指针,但不能安全地取消引用。

    【讨论】:

      【解决方案6】:

      您可以将 c 定义为大小为 1 的数组:

      char c[1] = { 'X' };

      然后未定义的行为将成为已定义的行为。 结果代码应该相同。

      【讨论】:

      • 您可以,但 1. 没有必要 2. 这不是用户的问题。这里没有未定义的行为,尽管我自己没有给出答案,但您的答案可能会遭到一些反对。
      • 一开始并不是未定义的行为。
      • 如果原始代码是未定义的行为,那么这段代码将是定义的行为。与有符号整数溢出类似的是未定义的行为(一些编译器使用它进行一些优化)。在那里,您可以使用从有符号类型到无符号类型的转换来获得定义的行为。
      猜你喜欢
      • 2014-10-16
      • 2012-08-09
      • 2015-04-19
      • 1970-01-01
      • 1970-01-01
      • 2012-04-21
      • 1970-01-01
      • 2013-05-30
      • 1970-01-01
      相关资源
      最近更新 更多