我认为这段代码应该警告超出范围的数组访问:
int foo() {
int x[10] = {0};
int *p = &x[5];
return p[~0LLU];
}
我知道标准不要求越界警告,但是编译器会给出警告。我在问编译器在这里发出这样的警告是否正确。
为什么认为该代码格式正确?
答案 0 :(得分:4)
我认为这段代码应该警告超出范围的数组访问:
当您在非VLA 阵列上执行此操作时,一个体面的编译器可以警告您(gcc不会,但是clang会:https://godbolt.org/z/lOvl5n)< / p>
此代码段:
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数组和文字索引是先决条件)
在您的情况下,分配给指针的是编译器的“丢失”(数组 decays 变成指针)
在那之后,编译器无法说出数据的来源,因此它无法控制边界(即使在您的情况下,偏移量大得令人讨厌/负数也是如此)。专用的静态分析工具可能会发现问题。
答案 1 :(得分:2)
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=%p\n", (void *)x, (void *)y, (void *)z);
return p[10] + y[0] + z[0];
}
即使p[10]
超出范围,此代码也不会发出警告。
因此,要执行越界检查及其执行方式取决于实施。
答案 2 :(得分:2)
编辑:使用标准引号进行完全重写:
[dcl.array] [注意:除为类声明的地方,下标运算符[]的解释方式为
E1[E2]
与{{ 1}}[expr.add]将具有整数类型的表达式添加到指针或从指针中减去时,结果具有该类型 指针操作数的值。如果表达式
*((E1)+(E2))
指向具有P
个元素的数组对象x[i]
的元素x
, 表达式n
和P + J
(其中J + P
的值为J
)指向(可能是假设的)元素j
,如果x[i + j]
;否则,行为是不确定的。
因此,0 ≤ i + j ≤ n
的解释与p[~0LLU]
相同(根据[dcl.array]),其中带括号的表达式指向元素*(p + ~0LLU)
-如果索引在有效范围内- (根据[expr.add])。如果索引不在范围内,则行为是不确定的。
x[5 + ~0LLU]
是否在索引的有效范围内?给定该语言的整数转换规则,如果5的类型是不大于5 + ~0LLU
的有符号类型,则所示的表达式似乎定义良好,在这种情况下,指向的元素将为{{1 }}。但是,标准并未在描述行为的表达式中明确定义unsigned long long
和x[4]
的类型。应该将其解释为纯数学表达式,在这种情况下,结果将是i
无法表示的索引,并且肯定大于j
,从而导致不确定的行为。
鉴于行为未定义的解释,对于编译器警告不会是不正确的。无论如何,不需要编译器发出警告。