【问题标题】:Array declared as int v[100] but &(v[100]) gives no warning数组声明为 int v[100] 但 &(v[100]) 没有给出警告
【发布时间】:2016-03-12 14:22:48
【问题描述】:

我有以下程序:

#include <stdio.h>

int main() {

    int v[100];
    int *p;

    for (p = &(v[0]); p != &(v[100]); ++p)
        if ((*p = getchar()) == EOF) {
            --p;
            break;
        }

    while (p != v)
        putchar(*--p);

    return 0;
}

这是gcc --version 在终端上的输出:

Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 7.0.2 (clang-700.1.81)
Target: x86_64-apple-darwin15.3.0
Thread model: posix

为什么在数组的最后一个之后获取元素的地址不会给我任何警告,但例如获取v[101] 的地址会给我以下警告

test.c:8:29: warning: array index 101 is past the end of the array (which
      contains 100 elements) [-Warray-bounds]
    for(p = &(v[0]); p != &(v[101]); ++p)
                            ^ ~~~
test.c:5:5: note: array 'v' declared here
    int v[100];
    ^
1 warning generated.

我知道索引元素超出缓冲区的范围是未定义的行为,那么为什么编译器不抱怨第一种情况呢?

【问题讨论】:

  • 如果在 EOF 之前只读取了零个字符,您的程序将调用 未定义的行为。
  • 你不应该在EOF之后减少p。
  • @chqrlie 你这么说是因为在下面的while循环中我先递减p然后取消引用它?
  • @nbro:完全正确。 p 指向第一个循环读取的最后一个有效字符的过去,因为您读取了 100 个字符或因为您到达了文件末尾。因此,当您从第一个循环中断时,不要增加 p。顺便说一句,这将解决 MikeCAT 在他的评论中暗示的问题。

标签: c arrays clang undefined-behavior


【解决方案1】:

除非您取消引用指针,否则允许将指针移到数组的最后一个元素之后,因此如果在到达 EOF 之前读取了一个或多个字符,则您的程序是有效的。

N12566.5.2.1 数组下标

下标运算符[]的定义 是 E1[E2] 等同于 (*((E1)+(E2)))。

N1256 6.5.3.2 地址和间接运算符

如果操作数是一元 * 运算符的结果, 该运算符和 & 运算符都没有被计算,结果就像两者都是 省略,除了对运算符的约束仍然适用并且结果不是 左值。同样,如果操作数是 [] 运算符的结果,则 & 运算符和 [] 隐含的一元 * 被评估,结果就像 & 运算符 被删除,[] 运算符被更改为 + 运算符。

N1256 6.5.6 加法运算符

此外,如果表达式 P 指向最后一个 数组对象的元素,表达式 (P)+1 指向数组对象的最后一个元素 数组对象,如果表达式 Q 指向数组对象的最后一个元素, 表达式 (Q)-1 指向数组对象的最后一个元素

【讨论】:

  • 老实说,引用并没有回答我的问题。您还说允许将指针移动到数组的最后一个元素之后(并且引用实际上与您所说的无关),但我 也 实际上得到了最后一个元素之后的元素的地址(通过首先访问它)...
  • @nbro 还有一个引用 &amp;*X 被定义为与 X 相同,因此当您将此与此答案中的第一个引用结合起来时,您会看到 &amp;v[100] 已定义表示(v + 100),根据最后的报价是合法的
  • 换一种说法,只有当您尝试访问指定的内存位置时,谈论 *(v+100) 才是未定义的(除了将 &amp; 或 sizeof 放在前面之外,您可以通过任何上下文来执行此操作它)
  • @M.M "还有一句名言 &*X 被定义为和 X 一样",在哪里?
  • @nbro C11 6.5.3.2/3, "[...] 如果操作数是 [] 运算符的结果,则既不是 & 运算符也不是 [] 所隐含的一元 *被评估,结果就好像 & 运算符被删除并且 [] 运算符被更改为 + 运算符"
【解决方案2】:

这是关于与草率编写的代码的兼容性。

正如 MikeCAT 所引用的,对于数组 int ar[N],表达式 ar+N 是有效的,并产生一个指向过去位置的指针。虽然这个指针不能被取消引用,但它可以与数组中的任何其他指针进行比较,这样您就可以编写漂亮的for (p = ar; p != ar+N; ++p) 循环。

此外,程序员喜欢编写可读代码,并且可以说,如果您想要一个指向数组的第 ith 元素的指针,编写 &amp;ar[i] 比编写 ar + i 更清楚地传达您的意图。

将这两者结合起来,你会得到编写 &amp;ar[N] 的程序员来获取过去指针,虽然这在技术上是访问无效的数组索引,但除了 @987654328 之外,没有编译器会实现它@ - 事实上,编译器必须不遗余力地做不同的事情。事实上还差得很远。

因此,由于任何不严格推理未定义行为的编译器都会执行程序员对表达式所期望的事情,因此没有理由不编写它,因此很多人都编写了它。现在我们有大量的代码库使用这个成语,这意味着即使是现代编译器也必须支持这个成语以实现兼容性。而且由于 Clang 的警告是有用的,所以写这个特别的警告是为了不警告一个无论如何都会起作用的案例,出于某种错误的迂腐感。

【讨论】:

  • 这是关于与草率编写的代码的兼容性。我不同意。在数组末尾构造一个指针一点也不草率。行为定义明确,是一种有用的技术。
猜你喜欢
  • 1970-01-01
  • 2018-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-10-14
  • 2022-01-08
  • 2020-09-19
相关资源
最近更新 更多