1

在我们的讲座中,我们最近查看了关于指针相等的 c99 标准(6.5.9.6)并将其应用于嵌套数组。那里指出,只有在“一个是指向一个超过一个数组对象末尾的指针,另一个是指向另一个数组对象的开头的指针,该数组对象恰好紧跟在第一个数组对象之后,才能保证指针相等。地址空间”。

教授随后解释说,这就是数组访问 a[0][19] 在技术上对于尺寸为 4*5 的嵌套数组未定义的原因。这是真的?如果是这样,为什么要定义负索引,例如 a[1][-1]?

4

1 回答 1

1

C 标准也a[0][19]没有a[1][-1]定义行为。

C 2018 6.5.2/1 2 告诉我们数组下标是根据指针算术定义的:

后缀表达式后跟方括号[]中的表达式是数组对象元素的下标指定。下标运算符的定义[]E1[E2]……相同(*((E1)+(E2)))。</p>

因此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 + 19q + -1,其中pq分别是这些第一个元素的地址,&a[0][0]a[1][0]

C 2018 6.5.6 8 定义了指针运算:

如果指针操作数指向数组对象的元素,并且数组足够大,则结果指向与原始元素偏移的元素,使得结果和原始数组元素的下标之差等于整数表达式。换句话说,如果表达式P指向数组对象的第i(P)+N个元素,则表达式(等价地,N+(P))和(P)-N(其中N的值为n)分别指向第i + n个和第i - n个数组对象的元素,前提是它们存在。此外,如果表达式P指向数组对象的最后一个元素,表达式(P)+1指向数组对象的最后一个元素,如果表达式Q指向数组对象的最后一个元素,则表达式(Q)-1指向数组对象的最后一个元素。如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。

如果它存在,那么p + 19将指向元素 19 。a[0]buta[0]是一个由 5 个元素组成的数组,因此元素 19 不存在,因此p + 19标准没有定义的行为。

类似地,q + -1将指向 的元素 -1 a[1],但元素 -1 不存在,因此q + -1标准未定义 的行为。

这些数组包含在一个更大的数组中,并且我们知道这个更大数组中所有元素的内存布局这一事实并不重要。C 标准没有根据更大的内存布局来定义行为;它基于正在评估指针算术的特定数组指定行为。AC 实现可以自由地使这种算术像简单的地址算术一样工作,并根据需要定义行为,但它也允许不这样做。编译器优化多年来变得更加复杂和激进,它可能会根据 C 标准关于特定数组算法的规则转换这些表达式,而不考虑内存布局,这可能导致表达式失败(不像它们那样简单的地址算术)。

于 2020-10-21T12:09:44.220 回答