过去数组对象的最后一个元素的基本原理是什么?

时间:2014-12-14 18:26:16

标签: c c11

根据N1570(C11草案)6.5.6/8 添加剂运算符

  

此外,如果表达式P指向数组的最后一个元素   对象,表达式(P)+1指向一个超过最后一个元素   数组对象,如果表达式Q指向最后一个元素的一个   对于数组对象,表达式(Q)-1指向的最后一个元素   数组对象

子条款6.5.6/9还包含:

  

此外,如果表达式P指向数组的元素   对象或一个超过数组对象的最后一个元素,以及   expression Q指向同一个数组对象的最后一个元素   表达式((Q)+1)-(P)((Q)-(P))+1具有相同的值   -((P)-((Q)+1)),如果表达式P指向数组对象的最后一个元素之后,则值为零,即使表达式为   (Q)+1未指向数组对象的元素。 106)

这证明指针的算术是有效的:

#include <stdio.h>

int main(void)
{
    int a[3] = {0, 1, 2};
    int *P, *Q;

    P = a + 3; // one past the last element
    Q = a + 2; // last element

    printf("%td\n", ((Q)+1)-(P));
    printf("%td\n", ((Q)-(P))+1);
    printf("%td\n", -((P)-((Q)+1)));

    return 0;
}

我希望不允许指向数组越界的元素,因为取消引用作为未定义的行为(数组溢出),因此它使它具有潜在的危险性。这有什么理由吗?

2 个答案:

答案 0 :(得分:7)

指定要作为half-closed interval [start, end)进行循环的范围,特别是对于数组索引,具有某些令人满意的属性,正如Dijkstra在one of his notes中观察到的那样。

1)您可以将范围的大小计算为end - start的简单函数。特别是,如果范围是根据数组索引指定的,则循环执行的迭代次数将由end - start给出。如果范围为[start, end],则迭代次数为end - start + 1 - 非常烦人,不是吗? :)

2)Dijsktra的第二次观察仅适用于(非负)积分指数的情况 - 指定范围为[start, end)(start, end]都具有1)中提到的属性。但是,将其指定为(start, end]要求您允许-1的索引表示包含索引0的循环范围 - 您允许&#34;非自然&#34;值-1只是为了表示范围。 [start, end)约定没有这个问题,因为end是一个非负整数,因此在处理数组索引时是一个很自然的选择。

Dijsktra反对允许-1与允许一个人超过容器的最后一个有效地址有相似之处。但是,由于上述惯例已经使用了很长时间,它很可能说服标准委员会做出这个例外。

答案 1 :(得分:3)

理由非常简单。不允许编译器将数组放在内存的末尾。为了说明,假设我们有一个16位指针的16位机器。低地址是0x0000。高地址是0xffff。如果您声明char array[256]且编译器在地址array找到0xff00,那么从技术上讲,该数组将适合内存,使用地址0xff000xffff。但是,表达式

char *endptr = &array[256];   // endptr points one past the end of the array

等同于

char *endptr = NULL;          // &array[256] = 0xff00 + 0x0100 = 0x0000

这意味着以下循环不起作用,因为ptr永远不会少于0

for ( char *ptr = array; ptr < endptr; ptr++ )

所以你引用的部分只是律师代言,&#34;不要将数组放在内存区域的末尾&#34;


历史记录:最早的x86处理器使用分段存储器方案,其中存储器地址由16位指针寄存器和16位段寄存器指定。通过将段寄存器向左移位4位并将指针加到指针上来计算最终地址。

pointer register    1234
segment register   AB00
                   -----
address in memory  AC234

结果地址空间为1MByte,但每64K字节有一个内存终止边界。这是使用律师发言而不是陈述的一个原因,“不要把数组放在记忆的末尾”#34;用简单的英语。