2D数组索引 - 未定义行为?

5

最近我看到了一些有问题的二维数组索引操作的代码。以以下代码为例:

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

以上索引操作是否存在未定义行为?

3
有一个很好的重复内容,但我找不到它,SO的搜索功能比人们的记忆要差得多。 - M.M
1
可能是重复问题在这里,不确定是否应该关闭此问题,因为另一个问题的提问方式不好,此外,这里接受的答案更好... - Aconcagua
2个回答

6
这是未定义行为,以下是原因。
多维数组访问可以分解为一系列单维数组的访问。换句话说,表达式 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][j] 在边界内,但如果 a[i] 不是有效的索引,则行为立即变成未定义行为。
在第一个示例中,a[0] 是有效的,但是后面的 [20] 不是,因为 a[0] 的类型是 int[5]。因此,索引 20 超出了范围。
在第二个示例中,a[-1] 已经超出了范围,因此已经是未定义行为。
但在最后一个示例中,表达式 a[5] 指向数组的最后一个元素之后,根据 §6.5.6/8 是有效的:

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

然而,在同一段落的后面:

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

因此,虽然 a[5] 是一个有效的指针,但对它进行解引用将导致未定义的行为,这是由最后的 [-3] 索引引起的(也超出范围,因此产生未定义行为)。

尽管它会衰变为 int *,但它仍然是一个指向数组的指针(我倾向于认为该数组只有5个元素)。 - Drew McGowen
a[5] 没有被解引用,但 a[5]-3 被解引用了(这可能是有效的)... 标准明确指出数组是连续存储的(没有填充和递增地址),并且明确声明越界访问是未定义行为。我不太理解它们如何同时存在... - mafso
2
@mafso 实际上,a[5]-3 表示对 a + 5 进行解引用。正如我所提到的,a[5][-3] 等同于 *(*(a + 5) - 3);表达式 *(a + 5) 是未定义的行为。 - Drew McGowen
1
请记住,指针也可以存储它所指向的对象的边界,这样一个边界检查的实现就是合法的。边界由被指向对象所属的对象确定。 - M.M
@MattMcNabb:我同意。谢谢! - mafso
显示剩余4条评论

-1

使用负索引进行数组索引是未定义的行为。抱歉,大多数架构/编译器中的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添加到该值中,以获取数据的确切地址。

敬礼


int a[][3][2]; 是非法的。您必须指定第一维,或者给出一个初始化程序,从中可以计算出第一维。它不是“指针别名”。您可能会混淆数组声明符的含义 [在函数参数列表中] (https://dev59.com/amEh5IYBdhLWcg3wNxEn),但在这种情况下, int a [4] [3] [2] 也是 int (*a) [3] [2] - M.M
&a + i * (sizeof... 这里,你的意思是 (char *)&a;指针算术运算是根据所指向的对象的大小来进行的。 - M.M
a[i][j][k]дёҺ*(*(*(a+i)+j)+k)зӣёеҗҢпјҲиҜ·жіЁж„Ҹзјәе°‘&пјүгҖӮ - M.M

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接