C指针算术运算与数组

12
我正在阅读K&R中有关数组运算的部分,并发现了一些奇怪的东西。我为了方便理解,贴出了整个段落,但我主要关注加粗部分。
如果p和q指向同一数组的成员,则诸如==、!=、<、>=等关系将正常工作。例如,如果p指向比q更早的数组成员,则p < q为真。任何指针都可以与零进行有意义的相等或不相等比较。但对于不指向同一数组的指针进行算术或比较的行为是未定义的。(有一个例外: 可以在指针算术中使用超出数组末尾的第一个元素的地址)。
这个例外的原因是什么?在定义它们的大小时,是否为任何数组分配了额外的内存空间?如果是,是为了什么目的?它是用来结束数组并添加一个空字符吗?

1
最简单的理解方式是:a[b] = a + b。请参阅第98页以获取解释。 - SheetJS
2
@Nirk:嗯?这和这个有什么关系呢? - jason
4个回答

11

原因是你可以在循环中像这样增加指针:

char a[42], *p;

for (p = a; p < &a[sizeof a]; p++)  // or p != &a[sizeof a]
{
   /* ... */
}

如果没有这个额外规则,这将是未定义的行为,因为指针将无效。


6
当定义数组的大小时,是否会为其分配额外的内存?不是。你引用的文本背景很重要。加粗的异常情况是针对指针算术(和关系)的。它表明,如果您在指向不属于同一数组成员的指针之间进行指针运算,则会出现 udb。然而,有一个单一的例外情况,即如果任一指针指向超出数组末尾的第一个元素。
如果是这样,目的是什么?不存在,因为它基于错误的前提。
将数组以空字符结束的吗?不是。
这是为了使得与数组末尾的比较合法,也就是当a是一个数组时,可以与&a[sizeof a]进行比较。请注意,&a[sizeof a]是数组结束后的第一个元素。如果p是指向a的元素或数组结束后的第一个元素,则可以将p&a[sizeof a]进行比较。引用自C99 specification第6.5.8.5节。
当比较两个指针时,结果取决于所指向对象在地址空间中的相对位置。如果两个指向对象或不完整类型的指针都指向同一个对象,或都指向同一数组对象的最后一个元素之后,它们将相等。如果所指向的对象是同一聚合对象的成员,则声明在结构中较晚的结构成员的指针比先前声明的成员的指针更大,并且具有较大下标值的数组元素的指针比相同数组的较低下标值的元素的指针更大。如果表达式P指向数组对象的元素,而表达式Q指向同一数组对象的最后一个元素,则指针表达式Q+1比P更大。在所有其他情况下,行为是未定义的。

2

数组的末尾没有额外的内存分配。它只是表示您可以在指针算术中使用下面标记为“End”的地址。Begin指向数组的第一个元素。End指向数组末尾的第一个元素之后

-----------------
|   |   |   |   |
-----------------
^               ^
Begin           End

1
你只需要计算数组末尾对象的地址,保证不会出问题,但不能解引用该指针。一个例子是,如果一个对象恰好分配在内存的末尾,计算地址时会导致算术溢出,这时就需要保证指针不会指向 NULL。否则,比较结果可能会被反转,可能会触发各种警报或者计算错误的地址。因此,编译器和链接器的责任是确保这种情况不会发生,程序员的责任是确保编译器和链接器只对这一种简单情况负责,并且当你跑到末尾之后的 n 个元素时,它们不必维护相同的保证。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接