在C语言中,指针减法何时是未定义的行为?

12
char *buf = malloc(bufsize)
char *ptr = buf;
…
while(condition) {
    ptrdiff_t offset = ptr - buf;    // <========== THIS LINE

    // offset will never be negative because we only ever *increase* ptr
    if ((size_t)offset > bufsize) {
        // we need more room
        bufsize += 128;
        buf = realloc(buf, bufsize);
        ptr = buf + offset;  // buf might be in a completely new location
    }
    *ptr++ = …  // write this byte
}

这个是 有效的 还是 未定义的

我一开始以为是有效的,但我读到了一些关于它是未定义的东西,所以我在谷歌上搜索了一下。这些链接似乎无法逃脱地声称它是未定义的:

然而,这些 SO 问题中都没有提到它:

所有这些都谈论了不是指向同一“数组”的两个指针。那实际上是指栈上的一个普通的 C 数组吗?

如果它是未定义的,那对我来说似乎非常奇怪...为什么要强制我携带一个计数器变量,当我可以访问一个常量指针和一个移动指针?

3个回答

7

malloc函数返回的内存块中的指针被视为指向同一数组:

7.22.3 内存管理函数

1 - 如果分配成功,malloc函数返回的指针可以被赋值给任何类型的对象的指针,然后可以用来访问在已分配空间中的该对象或该对象的数组(直到显式释放该空间)。


谢谢。我不确定这只是使用了不同的术语,还是我漏掉了什么。 - mk12
标准的某些部分会暗示,即使索引项在相同的分配区域内,对于任何超出0..4范围的y值,lvalue p->arr[x][y];也不会被定义,而不管x的值如何。目前还不清楚需要对p->arr[y]做什么才能获得一个指针,以便可以对该区域内的所有地址进行索引。 - supercat

2
ptrdiff_t offset = ptr - buf;    // <========== THIS LINE

这是完全定义好的行为。

(C99,6.5.6p9) "当两个指针相减时,它们都必须指向同一数组对象的元素[...]"


是的,但正是“…相同数组对象的元素…”让我感到困惑。一个malloc分配的内存块是否是“数组对象”? - mk12
ecatmur在他的回答中引用了标准中相关的段落。 - ouah
顺便提一下,即使这样定义:int a = 0; int *p = &a; int *q = &a + 1; ptrdiff_t d = p - q; - ouah

2

只要不超过数组末尾一个元素,这是定义行为。C99 §6.5.6/8对于添加指针和整数有以下规定:

[...] 如果指针操作数和结果都指向同一数组对象的元素或该数组对象的最后一个元素之一,则评估不应产生溢出;否则,行为未定义。 [...]

第9段,关于减法:

9) 当两个指针相减时,两者都应指向同一数组对象的元素或该数组对象的最后一个元素之一; [...]

来自§7.20.3/1:

如果分配成功,则返回的指针具有适当的对齐方式,以便可以将其分配给任何类型的对象的指针,然后用于访问在分配的空间中分配的此类对象或此类对象的数组(直到显式释放空间)。

因此,一旦您将ptr移动到指向最后一个数组元素之后的元素,执行指针减法就是未定义的行为。

我确信有些系统会对这段代码表现出不良反应,尽管我无法列举出来。理论上,malloc()可能会返回一个指向可寻址内存末尾之前的指针,例如,在32位系统上请求255字节时,它可能会返回0xFFFFFF00,因此创建超出末尾的指针将导致溢出。指针表示中的整数溢出也可能触发某种陷阱(例如,如果指针存储在特殊寄存器中)。虽然我不知道具有这些属性的任何系统,但C标准肯定允许它们存在。


所以,一旦您将ptr移动到超出最后一个数组元素之后的位置,执行指针减法就是未定义的行为。除非ptr恰好指向最后一个数组元素之后的位置。 - ouah
从理论上讲,malloc可能会返回指向可寻址内存结束之前的指针,例如在32位系统上请求256字节时,它可能会返回0xFFFFFF00。但实际上它不可能这样做,因为存在“数组最后一个元素之后”的规则。C标准在一则非规范性脚注中指出:“从这个角度来看,实现只需要在对象结束后提供一个额外的字节(可能与程序中的另一个对象重叠),以满足“超过最后一个元素”的要求。” - ouah
2
抱歉,我错过了只逐个字节递增的部分,请查看更新。逐个字节进行是可以的,但如果您跳过较大的增量并超过一个末尾,则行为未定义。 - Adam Rosenfield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接