在这种情况下,C标准对行为没有任何要求,但是许多实现在许多情况下确实指定了指针算术的行为,超出了标准(包括该标准)所要求的最低要求。
在任何符合标准的C实现以及几乎所有(如果不是全部)类似C的方言的实现中,以下保证将适用于任何指针p
,以使*p
或*(p-1)
标识某些对象:
- 对于
z
等于零的任何整数值,指针值(p+z)
和(p-z)
在各个方面都将等效于p
,不同之处在于,只有当p
和z
都恒定时,它们才会是恒定的。
- 对于
q
等于的任何p
表达式,p-q
和q-p
都将产生零。
对所有指针值(包括null)保持这种保证可以消除对用户代码中某些null检查的需要。此外,在大多数平台上,生成对所有指针值都保持这种保证而又不考虑它们是否为空的代码,比专门处理空值要简单和便宜。但是,某些平台可能会陷入尝试使用空指针执行指针算术的尝试,即使加或减零也是如此。在这样的平台上,在许多情况下必须添加到指针操作以维护保证的编译器生成的空检查的数量将大大超过用户生成的空检查的数量,其结果可能会被忽略。
如果存在这样一种实现,即维持保证的成本会很高,但是很少有程序会从中获得任何好处,那么允许它捕获“空+零”计算并要求用户代码这样的实现包括手动进行null检查,以确保可以不必进行担保。预计这一津贴不会影响其余99.44%的实施,因为这些实施的担保价值将超过成本。这样的实现应该维护这样的保证,但是他们的作者不需要标准的作者告诉他们。
C ++的作者已经决定,即使在可能严重降低指针算术性能的平台上,兼容的实现也必须不惜一切代价维护上述保证。他们认为,即使在维护成本很高的平台上,担保的价值也将超过成本。这种态度可能受到了将C ++视为比C语言更高级的语言的渴望的影响。可以期望AC程序员知道特定的目标平台何时以异常的方式处理类似(null + zero)的情况,但是C ++程序员没想到自己会关心这些事情。因此,保证一致的行为模型是值得的。
当然,如今有关“定义的”的问题很少与平台可以支持的行为有关。取而代之的是,现在,对于编译器而言,以“优化”的名义,要求程序员手动编写代码来处理平台以前可以正确处理的极端情况,已成为一种时尚。例如,假设应该n
从地址开始输出字符的代码p
写为:
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“不可能为null”,因此可以省略任何后续的null指针检查,从而导致代码陷入与实际“问题”无关的位置。