根据N1570(C11草案)6.5.6/8
添加剂运算符:
此外,如果表达式
P
指向数组的最后一个元素 对象,表达式(P)+1
指向一个超过最后一个元素 数组对象,如果表达式Q
指向最后一个元素的一个 对于数组对象,表达式(Q)-1
指向的最后一个元素 数组对象
子条款6.5.6/9
还包含:
此外,如果表达式
P
指向数组的元素 对象或一个超过数组对象的最后一个元素,以及 expressionQ
指向同一个数组对象的最后一个元素 表达式((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;
}
我希望不允许指向数组越界的元素,因为取消引用作为未定义的行为(数组溢出),因此它使它具有潜在的危险性。这有什么理由吗?
答案 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
,那么从技术上讲,该数组将适合内存,使用地址0xff00
至0xffff
。但是,表达式
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;用简单的英语。