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

时间:2019-01-17 14:53:57

标签: c++ c

我认为这段代码应该警告超出范围的数组访问:

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

我知道标准不要求越界警告,但是编译器会给出警告。我在问编译器在这里发出这样的警告是否正确。

为什么认为该代码格式正确?

3 个答案:

答案 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,   表达式nP + 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 longx[4]的类型。应该将其解释为纯数学表达式,在这种情况下,结果将是i无法表示的索引,并且肯定大于j,从而导致不确定的行为。

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