在我们的讲座中,我们最近研究了指针相等性(c.6.5.9.6)的c99标准,并将其应用于嵌套数组。那里指出只有在“一个是指针 指向一个数组对象的末尾,另一个是指向另一个数组的开始的指针 恰好紧跟地址中第一个数组对象的数组对象 空间”。
然后,教授解释了这是为什么对尺寸为4 * 5的嵌套数组未定义数组访问a [0] [19]的原因。这是真的?如果是这样,为什么要定义负数索引呢? a [1] [-1]?
答案 0 :(得分:1)
a[0][19]
和a[1][-1]
都没有C标准定义的行为。
C 2018 6.5.2 / 1 2告诉我们,数组下标是根据指针算术定义的:
后缀表达式后跟方括号
[]
中的表达式是数组对象元素的下标名称。下标运算符[]
的定义是E1[E2]
与(*((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,它们将自动转换为指向其第一个元素的指针。因此,这些表达式等效于p + 19
和q + -1
,其中p
和{{ 1}}分别是前两个元素q
和&a[0][0]
的地址。
C 2018 6.5.6 8定义了指针算法:
如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,以使结果数组元素和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式
a[1][0]
指向数组对象的第 i 个元素,则表达式P
(相当于(P)+N
)和{{ 1}}(其中N+(P)
的值为 n )分别指向 i + n -th和 i - n 个元素(如果存在)。此外,如果表达式(P)-N
指向数组对象的最后一个元素,则表达式N
指向数组对象的最后一个元素,如果表达式P
指向数组的最后一个元素数组对象的最后一个元素,表达式(P)+1
指向数组对象的最后一个元素。如果指针操作数和结果都指向同一数组对象的元素,或者指向数组对象的最后一个元素,则求值不应产生溢出。否则,行为是不确定的。
因此Q
将指向(Q)-1
的元素19(如果存在)。但是p + 19
是由5个元素组成的数组,因此元素19不存在,因此a[0]
的行为未由标准定义。
类似地,a[0]
指向p + 19
的元素-1,但是元素-1不存在,因此q + -1
的行为未由标准定义。
这些数组包含在一个较大的数组中,并且我们知道此较大数组中所有元素的内存布局这一事实无关紧要。 C标准没有根据更大的内存布局来定义行为。它根据要评估指针算术的特定数组指定行为。 C实现可以自由地使此算术像简单地址算术一样工作,并在需要时定义行为,但也可以不这样做。多年来,编译器优化已经变得更加复杂和激进,并且它可以基于C标准关于特定数组算术的规则来转换这些表达式,而不考虑内存布局,并且这可能导致表达式失败(表现不佳)简单的地址算法)。