过去数组对象的最后一个元素的基本原理是什么?
根据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
,那么从技术上讲,数组将适合内存,使用地址0xff00
到0xffff
包括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字节有一个内存终止边界。 这是使用律师说话的一个原因,而不是用简单的英语说“不要把数组放在记忆的末尾”。