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

我最近遇到了一些代码,做了一些有问题的2D数组索引操作。 以下面的代码示例为例:

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

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

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

多维数组访问可以分解为一系列单维数组访问。 换句话说,表达式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 - 3) 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到该值以获取数据的确切地址。

BR