假设我有一个 for 循环,它使用如下指针将零存储在数组中:
int *vp, values[5];
for(vp = &values[5-1]; vp >= &values[0]; vp--)
*vp = 0;
这本书 - C 上的指针说这个循环有问题,因为比较vp >= &values[0]
是未定义的,因为它移动到了数组的边界之外。但是如何?
即使排除旧的或奇异的处理器架构,此代码也不安全。
编译器中的优化器嵌入了许多关于语言的规则。当它看到vp >= &values[0]
, wherevalues
是一个数组时,优化器有权假定它vp
指向一个数组元素或数组之外的元素,因为否则表达式不是由 C 语言定义的。
因此,嵌入在优化器中的规则和机制可能会决定这vp >= &values[0]
始终是正确的,因此它可能会生成就像for (vp = &values[5-1]; ; vp--)
已经编写好的代码一样。这会导致一个没有终止条件的循环,并且在使用指向数组外部*vp = 0
进行评估时会产生进一步的未定义行为。vp
假设一个指针等价于一个无符号整数,我们可以看到问题只有values
从地址 0 开始才会存在,在这种情况下,指针会在递减后回绕并变为UINT_MAX
。
为了可视化问题,让我们逐步了解发生了什么,假设values
从地址 0x0 开始:
iteration 1:
vp = 0x4, *vp = 0;
iteration 2:
vp = 0x3, *vp = 0;
iteration 3:
vp = 0x2, *vp = 0;
iteration 4:
vp = 0x1, *vp = 0;
iteration 5:
vp = 0x0, *vp = 0;
iteration 6:
vp = 0xFFFFFFFF; *vp = ?? // uh oh!
因此,vp
永远不会小于指针的最小值(即 0),并且会导致无限循环(假设所有内存都是可写的)或分段错误。
根据标准,它也是未定义的行为(因为您可以在数组之后处理一个元素,但不能在它之前处理),但实际上,这在任何实际系统上都不会失败。
int values[5];
size_t idx;
for(idx=5; idx-- > 0; ) {
values[idx] = 0;
}
乍一看这可能看起来很奇怪,但一旦你习惯了它,它就会成为一个很好的模式。
顺便说一句:如果您坚持使用指针,则可以执行以下操作:
int *vp, values[5];
for(vp = &values[5-1]; vp ; vp = (vp > &values[0]) ? vp-1 : NULL; ) {
*vp = 0;
}