我正在阅读K& R中关于阵列算术的部分,并且遇到了一些奇怪的事情。我发布了整个段落的上下文,但我主要关注大胆的部分。
如果p和q指向同一个数组的成员,那么关系就像==, !=,&lt;,&gt; =等,正常工作。例如,p <如果p指向,则q为真 比q更早到数组的早期成员。任何指针都可以 有意义地将平等或不平等与零进行比较。但是 行为未定义用于算术或与指针比较 不要指向同一个数组的成员。 (有一个例外: 可以使用超过数组末尾的第一个元素的地址 在指针算术中。)
这种例外的原因是什么?在定义大小时,是否将额外的内存分配给任何数组的末尾?如果是这样,为了什么目的?是用空字符结束数组吗?
答案 0 :(得分:8)
原因是你可以像这样在循环中递增指针:
char a[42], *p;
for (p = a; p < &a[sizeof a]; p++) // or p != &a[sizeof a]
{
/* ... */
}
如果没有额外规则,这将是未定义的行为,因为指针无效。
答案 1 :(得分:4)
在定义大小时,是否有额外的内存分配给任何数组的末尾?
没有。您引用的上下文非常重要。粗体的例外是引用指针算术(和关系)。它说如果你在 not 指向同一个数组的成员的指针之间做指针关系,那么你得到udb。但是,有一个例外,即如果指针中的任何一个指向超过数组末尾的第一个元素。
如果是这样,为了什么目的?
null
回答,因为它假定是一个错误的前提。
是用空字符结束数组吗?
没有
这样做的原因是,与数组末尾的比较是合法的,即当&a[sizeof a]
是数组时,与a
的比较。请注意,&a[sizeof a]
是数组末尾的第一个元素。如果p
是指向a
元素的指针,或者是指向数组末尾的第一个元素,则可以将p
与&a[sizeof a]
进行比较。
我引用C99 specification,第6.5.8.5节。
当比较两个指针时,结果取决于指向的对象的地址空间中的相对位置。如果指向对象或不完整类型的两个指针都指向同一个对象,或者两个指针都指向同一个数组对象的最后一个元素,则它们相等。如果指向的对象是同一聚合对象的成员,则指向稍后声明的结构成员的指针比指向结构中先前声明的成员的指针大,指向具有较大下标值的数组元素的指针比指向同一数组的元素的指针大。具有较低的下标值。如果表达式
P
指向数组对象的元素,而表达式Q
指向同一数组对象的最后一个元素,则指针表达式Q + 1
将比{{1}更大}。在所有其他情况下,行为未定义。
答案 2 :(得分:0)
阵列末尾没有分配额外的内存。它只是说你可以在指针算术中使用下面标有'结束'的地址。开始指向数组的第一个元素。结束指向第一个元素过去数组的结尾。
-----------------
| | | | |
-----------------
^ ^
Begin End
答案 3 :(得分:0)
你只需要在数组末尾计算一个对象的地址,并承诺你不会在这样做时遇到麻烦。你不能取消引用那个指针。
该承诺重要的一个例子是,否则可能会在内存的最末端分配一个对象,这样当计算地址时,一个结尾的地址会导致算术溢出。如果你要通过该数组迭代指针,那么在最后一次迭代之后,算术溢出可能导致指针环绕并指向NULL。
这可能导致比较结果被反转,并且它可能会使用数组边界检查器触发各种警报铃声,或者如果CPU使用,例如饱和算术,它可能只是计算错误的地址。
因此,编译器和链接器有责任确保不会发生这种情况,并且程序员有责任确保编译器和链接器的责任仅限于一个简单的情况,并且它们不会当你进一步运行n
元素时,必须坚持相同的保证。