11

我正在阅读 K&R 中关于数组算术的部分,并遇到了一些奇怪的事情。我发布了整段的上下文,但我主要关注粗体部分。

如果 p 和 q 指向同一个数组的成员,则 ==、!=、<、>= 等关系可以正常工作。例如,如果 p 指向数组中比 q 更早的成员,则 p < q 为真。任何指针都可以有意义地比较是否等于或不等于零。但是对于算术或与不指向同一数组成员的指针的比较,行为是未定义的。(有一个例外:数组末尾之后的第一个元素的地址可用于指针算术。)

这个异常的原因是什么?定义数组大小时,是否将额外的内存分配给任何数组的末尾?如果是这样,出于什么目的?是否以空字符结束数组?

4

4 回答 4

11

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

char a[42], *p;

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

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

于 2013-06-18T15:40:04.633 回答
6

定义数组大小时,是否将额外的内存分配给任何数组的末尾?

不,您引用的上下文很重要。您加粗的例外是指指针算术(和关系)。这就是说,如果您在不指向同一数组成员的指针之间进行指针关系,那么您会得到udb。但是,有一个例外,即如果任何一个指针都指向数组末尾之后的第一个元素。

如果是这样,出于什么目的?

null回答,因为它假定了一个错误的前提。

是否以空字符结束数组?

不。

这样做的原因是,与数组末尾的比较是合法的,也就是说,与&a[sizeof a]when的比较a是一个数组。请注意,这&a[sizeof a]是数组末尾之后的第一个元素。如果p是指向a数组末尾元素或第一个元素的指针,则p可以与&a[sizeof a].

我引用了C99 规范,第 6.5.8.5 节。

比较两个指针时,结果取决于所指向对象在地址空间中的相对位置。如果两个指向对象或不完整类型的指针都指向同一个对象,或者都指向同一个数组对象的最后一个元素,它们比较相等。如果指向的对象是同一个聚合对象的成员,则指向稍后声明的结构成员的指针比较大于指向结构中较早声明的成员的指针,并且指向具有较大下标值的数组元素的指针比较大于指向同一数组的元素的指针具有较低的下标值。如果表达式P指向数组对象的一个​​元素,而表达式Q指向同一个数组对象的最后一个元素,则指针表达式Q + 1比较大于P. 在所有其他情况下,行为是未定义的。

于 2013-06-18T15:42:40.173 回答
1

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

-----------------
|   |   |   |   |
-----------------
^               ^
Begin           End
于 2013-06-18T15:42:20.977 回答
0

您可以简单地计算数组末尾的对象的地址,并且保证您不会遇到麻烦。不允许取消引用该指针。

这个承诺很重要的一个例子是,一个对象可能被分配在内存的最末端,因此当你计算地址时,末端的地址会导致算术溢出。如果您要通过该数组迭代一个指针,那么在最后一次迭代之后,算术溢出可能导致指针环绕并指向 NULL。

这可能会导致比较结果被反转,并且它可能会使用数组边界检查器触发各种警报,或者如果 CPU 使用饱和算法,它可能会简单地计算错误的地址。

所以编译器和链接器有责任确保这种情况不会发生,程序员有责任确保编译器和链接器的责任仅限于一个简单的情况,并且他们不必坚持当您n在最后运行元素时,同样的保证。

于 2013-06-18T16:11:19.667 回答