繁体   English   中英

在 c 中的二维数组上访问内部数组是否是未定义的行为

[英]Is it undefined behavior to access the inner array out of bounds on a 2d array in c

我正在玩一些 arrays 和 c 中的指针,并开始怀疑这样做是否会是未定义的行为。

int (*arr)[5] = malloc(sizeof(int[5][5]));

// Is this undefined behavior?
int val0 = arr[0][5];

// Rephrased, is it guaranteed it'll always have the same effect as this line?
int val1 = arr[1][0];

感谢您的任何见解。

在 C 中,您所做的是未定义的行为。

表达式arr[0]的类型为int [5] 因此,表达式arr[0][5]取消引用数组arr[0]末尾之后的一个元素,并且取消引用数组末尾之后是未定义的行为。

C 标准的第 6.5.2.1p2 节关于数组下标状态:

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

C 标准中关于加法运算符的第 6.5.6p8 节指出:

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

粗体部分指定数组下标中隐含的加法可能不会导致指针超过数组末尾一个元素,并且指向数组末尾后一个元素的指针可能不会被延迟。

所讨论的数组本身就是数组的成员,这意味着每个子数组的元素在 memory 中是连续的这一事实不会改变这一点。 编译器中的积极优化设置可能会注意到访问数组末尾并基于此事实进行优化是未定义的行为。

该标准显然旨在避免要求编译器给出以下内容:

int foo[5][10];
int test(int i)
{
  foo[1][0] = 1;
  foo[0][i] = 2;
  return foo[1][0];
}

必须重新加载foo[1][0]的值,以适应写入foo[0][i]可能影响foo[1][0]的可能性。 另一方面,在编写标准之前,编写如下内容是惯用的:

void dump_array(int *p, int rows, int cols)
{
  int i,j;
  for (i=0; i<rows; i++)
  {
    for (j=0; j<cols; j++)
      printf("%6d", *p++);
    printf("\n");
  }
}
int foo[5][10];
...
  dump_array(foo[0], 5, 10);

并且已发表的基本原理中没有任何内容表明作者有任何禁止此类构造或破坏使用它们的代码的意图。 事实上,要求数组的行连续放置的主要好处是,即使添加填充会提高效率,也允许这样的代码到 function。

在编写标准时,当为接收指针的 function 生成代码时,编译器会将指针视为可能识别某个任意较大 object 的任意部分,而无需努力了解或关心包含的内容object 可能是。 因此,作为一种非常流行的“符合语言扩展”形式,它们将支持诸如dump_array之类的结构,而不考虑标准是否要求它们这样做,因此标准的作者认为没有理由担心标准何时强制执行此类支持。 相反,他们留下了标准可以放弃管辖权的实施质量问题。

不幸的是,因为标准的作者预计编译器会将传递指向 function 的指针的行为视为隐式“清洗”它,所以标准的作者认为没有必要定义任何显式方法来清洗有关指针的封闭对象的信息在 function 需要处理标识“原始”存储的指针的情况下。 考虑到 1980 年代编译器技术的 state,这种区别并不重要,但如果例如代码执行以下操作,则可能非常相关:

int matrix[10][10];
void test2(int c)
{
  matrix[4][0] = 1;
  dump_array(matrix[0], 1, c);
  matrix[4][0] = 2;
}

或者

void test3(int r)
{
  matrix[4][0] = 1;
  dump_array((int*)matrix, r, 10);
  matrix[4][0] = 2;
}

根据函数的意图,让编译器优化对matrix[4][0]的第一次写入或两者都可以提高效率,或者可能导致生成的代码表现无用。 将显式指针转换视为擦除类型信息,但将数组到指针衰减视为保留它,如果程序员像第二个示例那样编写代码,将允许他们实现所需的语义,同时允许编译器在源代码执行时执行相关优化。写在第一个例子中。 不幸的是,标准没有任何区别,自由编译器的维护者不愿意放弃他们认为标准给予它们的任何“优化”,除了实现避免交叉- 程序优化或记录需要做什么来阻止它们。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM