这不应该发出越界警告吗?

Shouldn't this give an out-of-bounds warning?

本文关键字:警告 越界 不应该      更新时间:2023-10-16

我认为这段代码应该警告越界数组访问:

int foo() {
int x[10] = {0};
int *p = &x[5];
return p[~0LLU];
}

我知道标准不需要越界警告,但编译器确实给出了它们。我问编译器在这里给出这样的警告是否正确。

为什么应该认为该代码格式良好?

我认为这段代码应该警告越界数组访问:

一个体面的编译器可以在非VLA数组上执行此操作时警告您(gcc没有,但clang可以:https://godbolt.org/z/lOvl5n)

对于此代码段:

int foo() {
int x[10] = {0};  
return x[~0LLU];  // or x[40] to make it simpler, same thing
}

警告:

<source>:3:10: warning: array index -1 is past the end of the array (which contains 10 elements) [-Warray-bounds]
return x[~0LLU];
^ ~~~~~

编译器知道这是一个数组,知道大小,因此可以检查边界,如果一切都是文字的(非VLA数组和文字索引是先决条件)

在您的情况下,编译器"丢失"的是分配给指针(数组衰减为指针)

之后,编译器无法分辨数据的来源,因此它无法控制边界(即使在您的情况下,偏移量大得离谱/负数/其他什么)。专用的静态分析工具可能会发现问题。

C 语言对数组的边界检查没有要求。 这是使它变得快速的部分原因。 话虽如此,编译器可以并且确实在某些情况下执行检查。

例如,如果我在 gcc 中使用-O3编译并将return p[~0LLU];替换为return p[10];,则会收到以下警告:

x1.c: In function ‘foo’:
x1.c:6:10: warning: ‘*((void *)&x+60)’ is used uninitialized in this function [-Wuninitialized]
return p[10];

如果我使用-10作为索引,我会收到类似的警告:

gcc -g -O3 -Wall -Wextra -Warray-bounds -o x1 x1.c
x1.c: In function ‘foo’:
x1.c:6:10: warning: ‘*((void *)&x+-20)’ is used uninitialized in this function [-Wuninitialized]
return p[-100];

因此,它似乎可以警告数组索引的无效负值。

在您的情况下,对于此编译器来说,出于指针算术的目的,值~0LLU似乎被转换为有符号值,并被视为 -1。

请注意,可以通过将其他初始化变量放在x周围来欺骗此检查:

int foo() {
int y[10] = {0};
int x[10] = {0};
int z[10] = {0};
int *p = &x[5];
printf("&x=%p, &y=%p, &z=%pn", (void *)x, (void *)y, (void *)z);
return p[10] + y[0] + z[0];
}

即使p[10]超出范围,此代码也不会生成警告。

因此,如果它想要执行越界检查以及如何执行,这取决于实现。

编辑:完全重写,用标准引号:

[dcl.array] [注意:除非为类声明了下标运算符 [] 的解释方式使E1[E2]*((E1)+(E2))相同

[expr.add] 当在指针中添加或减去具有整型类型的表达式时,结果具有以下类型 的指针操作数。如果表达式P指向数组对象的元素x[i]xn元素, 表达式P + JJ + P(其中J的值为j)指向(可能假设的)元素x[i + j]如果0 ≤ i + j ≤ n;否则,行为是未定义的。

因此,p[~0LLU]的解释与*(p + ~0LLU)相同(根据 [dcl.array]),其中括号表达式指向元素x[5 + ~0LLU]- 如果索引在有效范围内 - (根据 [expr.add])。如果索引不在范围内,则行为未定义。

不过,5 + ~0LLU是否在指数的有效范围内?给定语言的整数转换规则,如果 5 的类型是不大于unsigned long long的有符号类型,则显示的表达式似乎是明确定义的,在这种情况下,尖点元素将是x[4]的。但是,标准没有在描述行为的表达式中明确定义ij的类型。它应该被解释为一个纯粹的数学表达式,在这种情况下,结果将是一个无法用long long unsigned表示的指数,并且肯定大于n,因此是未定义的行为。

鉴于行为未定义的解释,编译器发出警告并不正确。无论如何,编译器不需要发出警告。