【问题标题】:2D Array indexing - undefined behavior?二维数组索引 - 未定义的行为?
【发布时间】:2014-08-05 13:04:31
【问题描述】:

我最近研究了一些代码,做一些有问题的二维数组索引操作。以以下代码示例为例:

int a[5][5];
a[0][20] = 3;
a[-2][15] = 4;
a[5][-3] = 5;

上面的索引操作是否受到未定义行为的影响?

【问题讨论】:

  • 这个有很好的副本但是我找不到,SO搜索功能比人们的记忆差很多
  • 可能重复here,但不确定我们是否应该关闭这个,因为另一个不是很好的方式,另外,这里接受的答案更好......

标签: c arrays multidimensional-array undefined-behavior


【解决方案1】:

这是未定义的行为,原因如下。

多维数组访问可以分解为一系列单维数组访问。换句话说,表达式a[i][j] 可以被认为是(a[i])[j]。引用 C11 §6.5.2.1/2:

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

这意味着上面的内容与*(*(a + i) + j) 相同。遵循 C11 §6.5.6/8 关于添加整数和指针(强调我的):

如果两个指针 操作数和结果指向同一数组对象的元素,或最后一个元素 数组对象的元素,评估不应产生溢出; 否则, 行为未定义

换句话说,如果a[i] 不是有效索引,则行为会立即未定义,即使“直观地”a[i][j] 似乎在界限内。

所以,在第一种情况下,a[0] 是有效的,但下面的 [20] 不是,因为 a[0] 的类型是 int[5]。因此,索引 20 超出范围。

在第二种情况下,a[-1] 已经越界,因此已经是 UB。

然而,在最后一种情况下,表达式 a[5] 指向数组最后一个元素的后一个元素,根据 §6.5.6/8,这是有效的:

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

但是,在同一段的后面:

如果结果指向数组对象的最后一个元素,则不应将其用作计算的一元 * 运算符的操作数。

因此,虽然a[5] 是一个有效的指针,但取消引用它会导致未定义的行为,这是由最终的[-3] 索引引起的(这也是越界,因此是UB)。

【讨论】:

  • "[...] 因为a[0] 的类型是int[5] [...]"——这就是我卡住的部分。 a[0] 在这里进行左值转换,所以衰减到int *。不确定这个……
  • 即使它衰减到 int *,它仍然是一个指向数组的指针(我倾向于认为它只有 5 个元素)。
  • @mafso a[0] 的类型为 int[5] ;衰减的指针是一个右值(它指向一个由 5 个整数组成的数组的对象)
  • @mafso 实际上,a[5]-3 意味着 a + 5 被取消引用。正如我所提到的,a[5][-3] 等同于*(*(a + 5) - 3);表达式*(a + 5) 是UB。
  • 请记住,指针也可以存储它所指向的内容的边界,因此边界检查实现是合法的。边界由所指向的对象所属的对象决定。
【解决方案2】:

使用负索引的数组索引是未定义的行为。抱歉,a[-3] 在大多数架构/编译器中与*(&a - 3) 相同,并且在没有警告的情况下被接受,但是 C 语言允许您将负整数添加到指针,但不能使用负值作为数组索引。不幸的是,这甚至在运行时都没有检查。

此外,在指针前面定义数组时,还需要了解一些问题。您可以只保留第一个子索引,而不是更多,例如:

int a[][3][2]; /* array of unspecified size, definition is alias of int (*a)[3][2]; */

(确实,上面是指针定义,不是数组,直接打印sizeof a

int a[4][3][2]; /* 24 个整数的数组,大小为 24*sizeof(int) */

当你这样做时,计算偏移量的方法对于数组和指针是不同的,所以要小心。如果是数组,int a[I][J][K];

&a[i][j][k] 

放置在

&a + i*(sizeof(int)*J*K) + j*(sizeof(int)*K) + k*(sizeof(int))

但是当你声明时

int ***a; 

那么a[i][j][k] 等同于:

*(*(*(&a+i)+j)+k),这意味着你必须取消引用指针a,然后将(sizeof(int **))*i添加到它的值,然后再次取消引用,然后将(sizeof (int *))*j添加到该值,然后取消引用它,然后添加(sizeof(int))*k value 以获取数据的确切地址。

BR

【讨论】:

  • int a[][3][2]; 是非法的。您必须指定第一个维度,或者提供一个计算第一个维度的初始值设定项。它不是“指针别名”。您可能对数组声明符 in a function parameter list 的含义感到困惑,但在这种情况下,int a[4][3][2] 也是 int (*a)[3][2]
  • &a + i * (sizeof...你的意思是(char *)&a;指针运算是根据所指向对象的大小来完成的
  • a[i][j][k]*(*(*(a+i)+j)+k) 相同(注意缺少&
猜你喜欢
  • 1970-01-01
  • 2011-01-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-05
相关资源
最近更新 更多