7

假设我有一个 for 循环,它使用如下指针将零存储在数组中:

int *vp, values[5];

for(vp = &values[5-1]; vp >= &values[0]; vp--)
   *vp = 0;

这本书 - C 上的指针说这个循环有问题,因为比较vp >= &values[0]是未定义的,因为它移动到了数组的边界之外。但是如何?

4

3 回答 3

4

即使排除旧的或奇异的处理器架构,此代码也不安全。

编译器中的优化器嵌入了许多关于语言的规则。当它看到vp >= &values[0], wherevalues是一个数组时,优化器有权假定它vp指向一个数组元素或数组之外的元素,因为否则表达式不是由 C 语言定义的。

因此,嵌入在优化器中的规则和机制可能会决定这vp >= &values[0]始终是正确的,因此它可能会生成就像for (vp = &values[5-1]; ; vp--)已经编写好的代码一样。这会导致一个没有终止条件的循环,并且在使用指向数组外部*vp = 0进行评估时会产生进一步的未定义行为。vp

于 2013-05-12T17:06:45.433 回答
2

假设一个指针等价于一个无符号整数,我们可以看到问题只有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),并且会导致无限循环(假设所有内存都是可写的)或分段错误。

根据标准,它也是未定义的行为(因为您可以在数组之后处理一个元素,但不能在它之前处理),但实际上,这在任何实际系统上都不会失败。

于 2013-05-12T16:45:20.433 回答
0
int values[5];
size_t idx;

for(idx=5; idx-- > 0; ) {
   values[idx] = 0;
   }

乍一看这可能看起来很奇怪,但一旦你习惯了它,它就会成为一个很好的模式。

  • 你避免了指针值的(可能的)下溢(这是严格未定义的)
  • 您不能下溢,无符号类型(例如 size_t)会回绕,这肯定会导致非法访问,这比偷偷摸摸低位寻址更可取,后者会破坏内存而不是快速失败
  • WRT 提高速度/性能:从编译器的角度来看,指针和索引基本相同。编译器甚至可以为这两种情况生成相同的代码。

顺便说一句:如果您坚持使用指针,则可以执行以下操作:

int *vp, values[5];

for(vp = &values[5-1]; vp ; vp = (vp > &values[0]) ? vp-1 : NULL; ) {
   *vp = 0;
   } 
于 2013-05-18T18:20:13.453 回答