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

根据N1570(C11草案) 6.5.6/8 添加剂操作员

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

6.5.6/9还包括:

此外,如果表达式P指向数组对象的元素或者指向数组对象的最后一个元素,并且表达式Q指向同一数组对象的最后一个元素,则表达式((Q)+1)-(P)具有与((Q)-(P))+1-((P)-((Q)+1))的值,并且如果表达式P指向一个,则值为零即使表达式(Q)+1没有指向数组对象的元素,也是数组对象的最后一个元素。 106)

这certificate指针的算术是有效的:

 #include  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; } 

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

指定要作为半闭区间 [start, end)循环的范围,特别是对于数组索引,具有一定的令人愉悦的属性,如Dijkstra在他的一个笔记中观察到的。

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

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

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

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

 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++ ) 

因此,您引用的部分只是律师代言,“不要将数组放在内存区域的末尾”。


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

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

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