【问题标题】:Decrementing a pointer out of bounds; incrementing it into bounds [duplicate]递减指针越界;将其递增到界限[重复]
【发布时间】:2013-08-12 12:27:47
【问题描述】:

以下是否会在第 4 行和/或第 5 行引发未定义的行为:

#include <stdio.h>
int main(void)
{
  char s[] = "foo";
  char * p = s - 1;      /* line 4 */
  printf("%s\n", p + 1); /* line 5 */
  return 0;
}

【问题讨论】:

  • 已经有一段时间了,但是虽然相关的重复项实际上似乎并不是这个问题的重复项。我可以重新打开,但由于我是公认的答案,我会让其他人这样做。
  • @ShafikYaghmour:“...重复的 [...] 实际上似乎不是重复的...” 请问是什么原因?从您(以及其他人)回答第 4 行的内容实际上引发了 UB,链接问题中的 array - 1 也是如此。
  • 尽管主题相似,但它们并不是同一个问题,但在 this 元讨论之后,我发现自己对重复关闭更加持怀疑态度,但在这个主题上似乎存在广泛的意见分歧。
  • 我发现这种未定义行为实际上导致计算错误的情况(在普通 x86 上):stackoverflow.com/questions/23683029/…

标签: c pointers undefined-behavior


【解决方案1】:

在数组边界外递减指针是未定义的。

C99 standard item 6.5.6 第 8 段部分表示,

当一个整数类型的表达式被添加到指针或从指针中减去时, 结果具有指针操作数的类型。 ...如果两个指针 操作数和结果指向同一数组对象的元素,或最后一个元素 数组对象的元素,评估不应产生溢出;否则, 行为未定义。

所以你的第 4 行调用了未定义的行为,因为结果既不在数组内,也不在数组末尾。

【讨论】:

  • 评估不会产生”这句话让我很好奇指针是否真的被评估。并且“p + 1”肯定在数组内。
  • @CaptainGiraffe:未评估指针 - 表达式是并导致(类型)指针。在这种情况下,您有表达式 s - 1 在给定上下文中的评估会产生一些值。例如,对于 C[s -&gt; 00000001]s - 1 的计算结果为 00000000。我的阅读是 evaluation 在这里是抽象的,并且在数学空间中(你不写表达式,如果评估,将导致 ....)而不是小步骤执行的一部分。
  • @MaciejPiechotka 是的,您对文本的阅读似乎与其他答案一致。我不确定。我的印象是评估意味着调查那个地方;当然是UB。毕竟它应该只是常规的整数算术,有规律的表现(轶事推理,我知道)。
  • @CaptainGiraffe “我的印象是评估意味着调查那个地方”——你的印象是错误的——那是取消引用,而不是评估。 Maciej 也错了——求值不是抽象的,它指的是对指针运算产生的值的计算。
  • @cmaster 不,它不是这样工作的。编译器不必去寻找未定义的行为。 6.5.6 第 8 段中没有任何内容表明数组定义必须可见才能调用未定义的行为。
【解决方案2】:

是的,第 4 行是未定义的行为!

C99 6.5.6 加法运算符,第 8 节

当一个整数类型的表达式被添加到指针或从指针中减去时, 结果具有指针操作数的类型。如果指针操作数指向一个元素 一个数组对象,并且数组足够大,结果指向一个元素的偏移量 原始元素,使得结果和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式 P 指向数组对象的 i-th 元素,则表达式 (P) + N(等效于 N + (P))和 (P) - N(其中 N 的值是 n)指向,分别是数组对象的i+n-thi−n-th 元素,前提是它们存在。此外,如果表达式P 指向数组对象的最后一个元素,则表达式(P) + 1 指向数组对象的最后一个元素,如果表达式Q 指向数组对象的最后一个元素对象,表达式(Q) - 1 指向数组对象的最后一个元素。 如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元 * 运算符的操作数。

【讨论】:

    【解决方案3】:

    以下内容是否会在第 4 行和/或第 5 行引发未定义的行为:

    是的,第 4 行undefined behavior,因为指针未指向数组边界内或超出数组边界。尽管指向数组边界后的一个是有效的,但您不能取消引用该元素。

    c99 draft standard 中的相关部分是6.5.6 加法运算符第 8 段

    当一个整数类型的表达式被添加到指针或从指针中减去时, 结果具有指针操作数的类型。 [...] 如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则评估不会产生溢出; 否则,行为未定义

    段落的结尾说你不应该尊重最后一个元素:

    [...] 如果结果指向数组对象的最后一个元素,则它不应用作被评估的一元 * 运算符的操作数

    【讨论】:

    • 我花了很长时间才了解规范,但是是的。值得注意的是,在实践中这总是会奏效,因为要付出更多努力才能让它不起作用,但理论上并不能保证。
    • @chrylis 有 C 解释器会在第 4 行停止执行。
    • @JimBalter 我这周已经不得不考虑一次 CINT...抱头
    • @chrylis: “在实践中这总是可行的” -- 这是一个糟糕的假设。优化编译器可以假设代码的行为是明确定义的,并根据该假设转换代码。
    • @KeithThompson:作为一种特殊情况,如果编译器可以静态确定代码路径无条件导致未定义的行为,它可以简单地从输出中删除整个代码路径,因为程序的唯一方法可以避免出现未定义的行为是永远不会到达该代码路径。
    猜你喜欢
    • 2021-03-04
    • 1970-01-01
    • 2015-06-13
    • 1970-01-01
    • 2015-01-01
    • 2014-11-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多