减去两个NULL指针的行为是否定义?

是否定义了两个非空指针variables(每个C99和/或C ++ 98)的差异,如果它们都是NULL值?

例如,说我有一个缓冲区结构,看起来像这样:

 struct buf { char *buf; char *pwrite; char *pread; } ex; 

说, ex.buf指向一个数组或一些malloc'ed内存。 如果我的代码总是确保pwritepread指向那个数组或者是一个超过它,那么我相信ex.pwrite - ex.pread将总是被定义的。 但是,如果pwritepread都是NULL, 我可以只是期望减去两个被定义为(ptrdiff_t)0或严格符合的代码是否需要testing指针NULL? 请注意,我感兴趣的唯一情况是当两个指针都是NULL(这代表一个缓冲区未初始化的情况下)。 考虑到前面的假设得到满足,原因与完全符合“可用”函数有关:

 size_t buf_avail(const struct s_buf *b) { return b->pwrite - b->pread; } 

在C99中,这是技术上未定义的行为。 C99§6.5.6说:

7)对于这些运算符来说,指向非数组元素的对象的指针的行为与指向长度为1的数组的第一个元素的指针相同,该对象的types是元素types。

[…]

9)当减去两个指针时,都应指向同一个数组对象的元素,或者指向数组对象的最后一个元素之后的元素; 结果是两个数组元素的下标差异。 […]

并且§6.3.2.3/ 3说:

值为0的整数常量expression式,或者转换为void *types的expression式称为空指针常量。 55)如果一个空指针常量被转换为一个指针types,那么被调用的空指针所产生的指针将被保证与指向任何对象或函数的指针进行比较。

因此,由于空指针不等于任何对象,违反了6.5.6 / 9的前提条件,所以它是不确定的行为。 但实际上,我敢打赌,几乎每个编译器都会返回0的结果,而没有任何不良副作用。

在C89中,这也是未定义的行为,虽然标准的措辞略有不同。

另一方面,C ++ 03在这个例子中已经定义了行为。 该标准为减去两个空指针做了一个特殊的例外。 C ++ 03§5.7/ 7说:

如果将值0添加到指针值或从指针值中减去,则结果与原始指针值相等。 如果两个指针指向同一个对象,或者同时指向同一个数组的末尾,或者两个指针都为空,并且减去两个指针,则结果将等于转换为typesptrdiff_t的值0。

C ++ 11(以及C ++ 14,n3690的最新草案)与C ++ 03具有相同的措辞,只是std::ptrdiff_t代替了ptrdiff_t

我在C ++标准(5.7 [expr.add] / 7)中find了这个:

如果两个指针都为空,并且两个指针相减,则结果将等于转换为std :: ptrdiff_ttypes的值0

正如其他人所说的那样,C99需要两个指针之间的加/减是同一个数组对象。 NULL不指向一个有效的对象,这就是为什么你不能在减法中使用它。

编辑 :这个答案只对C有效,我回答时没有看到C ++标签。

不,指针算术只允许指向同一对象内的指针。 由于C标准定义的空指针并不指向任何对象,这是未定义的行为。

(虽然,我猜想任何合理的编译器将只返回0 ,但谁知道。)

C标准没有对行为施加任何要求,然而许多实现在许多情况下指定了标准所要求的最小值之外的指针算术的行为,包括从另一个中减去一个空指针的行为。 几乎没有什么体系结构可以让这种减法除了产生零分之外做任何其他的事情,但不幸的是编译器现在很时髦 – 以“优化”的名义 – 要求程序员手动编写代码以处理平台之前正确处理的angular落案件。 例如,如果应该输出从地址p开始的n字符的代码被写为:

 void out_characters(unsigned char *p, int n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); } 

如果p == NULL和n == 0,而不需要特殊的n == 0,则较旧的编译器将生成可靠地不输出任何内容,没有副作用的代码。 不过,在较新的编译器中,必须添加额外的代码:

 void out_characters(unsigned char *p, int n) { if (n) { unsigned char *end = p+n; while(p < end) out_byte(*p++); } } 

优化器可能会或可能无法摆脱。 不包括额外的代码可能会导致一些编译器认为,因为p“不可能为空”,任何后续的空指针检查可能被省略,从而导致代码打破在与实际“问题”无关的位置。