C99 嵌套数组未定义行为
C99 nested arrays undefined behaviour
在我们的讲座中,我们最近了解了关于指针相等性的 c99 标准 (6.5.9.6) 并将其应用于嵌套数组。那里指出,只有在“一个是指针”的情况下才能保证指针相等
一个指向一个数组对象的末尾,另一个是指向另一个数组对象开始的指针
地址中恰好紧跟在第一个数组对象之后的数组对象
space".
教授随后解释说,这就是数组访问 a[0][19] 在技术上对于尺寸为 4*5 的嵌套数组未定义的原因。这是真的?如果是这样,为什么要定义负索引,例如a[1][-1]?
a[0][19]
和 a[1][-1]
都没有 C 标准定义的行为。
C 2018 6.5.2/1 2告诉我们数组下标是根据指针运算定义的:
A postfix expression followed by an expression in square brackets []
is a subscripted designation of an element of an array object. The definition of the subscript operator []
is that E1[E2]
is identical to (*((E1)+(E2)))
…
因此 a[0][19]
等同于 *(a[0] + 19)
(其中省略了一些括号,因为它们是不必要的),并且 a[1][-1]
等同于 *(a[1] + -1)
.
在a[0] + 19
和a[1] + -1
中,a[0]
和a[1]
是数组。在这些表达式中,根据 C 2018 6.3.2.1 3,它们会自动转换为指向其第一个元素的指针。因此这些表达式等效于 p + 19
和 q + -1
,其中 p
和 q
分别是第一个元素的地址,&a[0][0]
和 a[1][0]
。
C 2018 6.5.6 8定义指针算法:
If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression P
points to the i-th element of an array object, the expressions (P)+N
(equivalently, N+(P)
) and (P)-N
(where N
has the value n) point to, respectively, the i + n-th and i − n-th elements of the array object, provided they exist. Moreover, if the expression P
points to the last element of an array object, the expression (P)+1
points one past the last element of the array object, and if the expression Q
points one past the last element of an array object, the expression (Q)-1
points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
因此 p + 19
将指向 a[0]
的元素 19(如果存在)。但是 a[0]
是一个包含 5 个元素的数组,所以第 19 个元素不存在,因此 p + 19
的行为没有被标准定义。
类似地,q + -1
将指向 a[1]
的元素 -1,但元素 -1 不存在,因此 q + -1
的行为未由标准定义。
这些数组包含在一个更大的数组中,并且我们知道这个更大数组中所有元素的内存布局这一事实并不重要。 C 标准没有根据更大的内存布局来定义行为;它根据正在评估指针算法的特定数组指定行为。 C 实现可以自由地使这种算术像简单的地址算术一样工作,并在需要时定义行为,但它也允许不这样做。多年来,编译器优化变得更加复杂和积极,它可能根据 C 标准关于特定数组算法的规则转换这些表达式,而不考虑内存布局,这可能导致表达式失败(不像它们的行为那样简单的地址运算)。
在我们的讲座中,我们最近了解了关于指针相等性的 c99 标准 (6.5.9.6) 并将其应用于嵌套数组。那里指出,只有在“一个是指针”的情况下才能保证指针相等 一个指向一个数组对象的末尾,另一个是指向另一个数组对象开始的指针 地址中恰好紧跟在第一个数组对象之后的数组对象 space".
教授随后解释说,这就是数组访问 a[0][19] 在技术上对于尺寸为 4*5 的嵌套数组未定义的原因。这是真的?如果是这样,为什么要定义负索引,例如a[1][-1]?
a[0][19]
和 a[1][-1]
都没有 C 标准定义的行为。
C 2018 6.5.2/1 2告诉我们数组下标是根据指针运算定义的:
A postfix expression followed by an expression in square brackets
[]
is a subscripted designation of an element of an array object. The definition of the subscript operator[]
is thatE1[E2]
is identical to(*((E1)+(E2)))
…
因此 a[0][19]
等同于 *(a[0] + 19)
(其中省略了一些括号,因为它们是不必要的),并且 a[1][-1]
等同于 *(a[1] + -1)
.
在a[0] + 19
和a[1] + -1
中,a[0]
和a[1]
是数组。在这些表达式中,根据 C 2018 6.3.2.1 3,它们会自动转换为指向其第一个元素的指针。因此这些表达式等效于 p + 19
和 q + -1
,其中 p
和 q
分别是第一个元素的地址,&a[0][0]
和 a[1][0]
。
C 2018 6.5.6 8定义指针算法:
If the pointer operand points to an element of an array object, and the array is large enough, the result points to an element offset from the original element such that the difference of the subscripts of the resulting and original array elements equals the integer expression. In other words, if the expression
P
points to the i-th element of an array object, the expressions(P)+N
(equivalently,N+(P)
) and(P)-N
(whereN
has the value n) point to, respectively, the i + n-th and i − n-th elements of the array object, provided they exist. Moreover, if the expressionP
points to the last element of an array object, the expression(P)+1
points one past the last element of the array object, and if the expressionQ
points one past the last element of an array object, the expression(Q)-1
points to the last element of the array object. If both the pointer operand and the result point to elements of the same array object, or one past the last element of the array object, the evaluation shall not produce an overflow; otherwise, the behavior is undefined.
因此 p + 19
将指向 a[0]
的元素 19(如果存在)。但是 a[0]
是一个包含 5 个元素的数组,所以第 19 个元素不存在,因此 p + 19
的行为没有被标准定义。
类似地,q + -1
将指向 a[1]
的元素 -1,但元素 -1 不存在,因此 q + -1
的行为未由标准定义。
这些数组包含在一个更大的数组中,并且我们知道这个更大数组中所有元素的内存布局这一事实并不重要。 C 标准没有根据更大的内存布局来定义行为;它根据正在评估指针算法的特定数组指定行为。 C 实现可以自由地使这种算术像简单的地址算术一样工作,并在需要时定义行为,但它也允许不这样做。多年来,编译器优化变得更加复杂和积极,它可能根据 C 标准关于特定数组算法的规则转换这些表达式,而不考虑内存布局,这可能导致表达式失败(不像它们的行为那样简单的地址运算)。