最近我看到了一些有问题的二维数组索引操作的代码。以以下代码为例:
int a[5][5];
a[0][20] = 3;
a[-2][15] = 4;
a[5][-3] = 5;
以上索引操作是否存在未定义行为?
最近我看到了一些有问题的二维数组索引操作的代码。以以下代码为例:
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][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 McGowena[5]
没有被解引用,但 a[5]-3
被解引用了(这可能是有效的)... 标准明确指出数组是连续存储的(没有填充和递增地址),并且明确声明越界访问是未定义行为。我不太理解它们如何同时存在... - mafsoa[5]-3
表示对 a + 5
进行解引用。正如我所提到的,a[5][-3]
等同于 *(*(a + 5) - 3)
;表达式 *(a + 5)
是未定义的行为。 - Drew McGowen使用负索引进行数组索引是未定义的行为。抱歉,大多数架构/编译器中的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.Ma[i][j][k]
дёҺ*(*(*(a+i)+j)+k)
зӣёеҗҢпјҲиҜ·жіЁж„Ҹзјәе°‘&
пјүгҖӮ - M.M