【问题标题】:Infinite loop when using size_t in a count down for loop在倒计时循环中使用 size_t 时的无限循环
【发布时间】:2017-04-25 20:16:41
【问题描述】:

所以我在任何索引 for 循环中使用 size_t 而不是 int 来防止负索引。但是在倒计时的时候,这会导致溢出:

for (size_t i = 10; i >= 0; --i) {
    // Do something, f.ex. array[i] = i
}

什么是防止这种情况的好方法?

  • 改用int
  • 使用 i == 0 作为终止条件? (但是,如果我需要 0,这将不起作用)

如有任何反馈,我将不胜感激!

【问题讨论】:

  • 从技术上讲,这不是溢出,因为size_t 是无符号类型,但它绝对是一个无限循环,因为终止条件始终为真。

标签: c loops infinite-loop size-t


【解决方案1】:
for (size_t i = 10; i <= 10; --i) // do something

当溢出确实发生时,它会四舍五入到最大的整数,因此条件将失败。

【讨论】:

  • @Stargateur 是的。我只是复制并添加了i &lt;= 10 部分。感谢您指出。
  • 控制循环的新方法:UV,但它看起来违反直觉。
  • 当我还是个小钳子的时候,我曾经这样做过,但被我的教授训斥了。
  • 我不建议任何人做这里展示的事情。它可能是对 OP 代码更改最小的解决方案。但是要么使用带符号的计数器,要么重写循环以使用增量更干净
【解决方案2】:
for (size_t i = 11; i-- > 0; ) {
    // Do something, f.ex. array[i] = i
}

注意:问题以 value=10 开始循环(这很奇怪,但并非不可能)。我从11开始,但是第一次进入循环体时,已经减到10了。

【讨论】:

  • 是的。问题以 value=10 开始循环(这很奇怪,但并非不可能)。我从11开始,但是第一次进入循环body时,已经递减了,
【解决方案3】:

从技术上讲,这不是溢出,因为size_t 是无符号类型,但它绝对是一个无限循环,因为终止条件始终为真。

无符号整数在0 处递减时环绕。请注意,您的循环将在环绕发生之前运行 11 次,而不是 10

您必须在减少索引之前检查条件。以比最大有效索引大一的初始值开始枚举可以提高视觉一致性并简化测试。

这是一个更正的版本,您可以看到i 的初始值是数组的元素数:

int array[11];
for (size_t i = 11; i-- > 0; ) {
    // Do something, f.ex. array[i] = i
}

【讨论】:

    【解决方案4】:

    虽然不是每个人都喜欢,但惯用的方式是使用幻灯片操作符:

    for (size_t i = 10 + 1; i--> 0; )
    

    它并不是一个真正的运营商,但多年来它已经成为众所周知的东西。

    【讨论】:

    • 我个人称之为downto操作符。
    【解决方案5】:

    最简单的方法是增加上限值。例如

    const size_t N = 10;
    
    for (size_t i = N + 1; i != 0; --i) {
        // Do something, f.ex. array[i-1] = i-1
    }
    

    const size_t N = 10;
    
    for (size_t i = N + 1; i-- != 0; ) {
        // Do something, f.ex. array[i] = i
    }
    

    一般情况下,当 i 可以等于 size_t 类型的对象中存储的最大值时,您可以使用以下技巧

    #include <stdio.h>
    
    int main( void )
    {
        const size_t N = 10;
    
        for (size_t i = N, j = N; !( i == 0 && j == -1 ); j--)
        {
            i = j;
            printf( "%zu ", i );
        }
    
        printf( "\n" );
    }
    

    否则你可以使用 do-while 循环。在这种情况下更合适。例如

    size_t i = N;
    
    do
    {
        printf( "%zu ", i );
    } while ( i-- != 0 );
    

    【讨论】:

    • 好吧,你赢了(领先 4 秒!)
    • @AdHominem 为什么不能使用 0?循环的初始值 0 + 1。所以你可以使用 0。
    • 我认为这个答案在数字上没有任何问题。而且很全面。加一。
    【解决方案6】:

    size_t i = 10; i &gt;= 0; 永远不会为假,因为size_t 是某种无符号类型,并且所有值都大于或等于零。

    ... size_tsizeof 运算符结果的无符号整数类型; ...
    C11 §7.19 2

    启用警告的良好编译器会对此发出警告。
    希望这种无限循环永远不会发生,因为对警告的调查会首先纠正问题。


    最佳选择取决于编码目标

    好的代码会避免像裸 10 这样的幻数。最好是代码派生出来。在这个简单的例子中,它应该是 11。

    #define A_SIZE 11
    int array[A_SIZE];
    ...
    for (size_t i = A_SIZE; i-- > 0; ) {
        // Do something, f.ex. array[i] = i
    }
    

    OTOH,代码可能在循环中有break 条件,需要在后面的代码中使用i 来指示array[] 用法

    size_t i = A_SIZE;
    while (i > 0) {
      if (...) break; 
      i--;
      // Do something, f.ex. array[i] = i
      if (...) break; 
    }
    // Do something with i
    

    代码可能有合同要求在不同的地方使用10

    // Contract says loop must handle indexes 0 to N, inclusive
    #define N 10
    int array[N + 1];
    
    for (size_t i = N; i + 1 > 0; i--) {
      // Do something, f.ex. array[i] = i
    }
    

    良好的优化编译器不会对每个i + 1 &gt; 0 执行+1,而是创建等效的高效代码。

    代码是一种最能传达代码整体含义的时尚。

    【讨论】:

    • 太棒了!我从没想过i + 1 &gt; 0 来测试环绕。它相当于i != SIZE_MAX,不那么难看,但可以说更有趣,因为它要求简化。没有涵盖一种情况:它不适用于N = SIZE_MAX,这在现代系统中相当罕见,但在 16 位保护模式时代并不少见。也可以使用i != -1,也有同样的缺点。
    • 确实不错。我想在实践中这很容易出错,因为你很容易忘记加 1。使用 SIZE_MAX 并不比i &lt;= n; --ii-- &gt; 0; 更违反直觉,它们都预料到了环绕。
    【解决方案7】:

    问题在于,在您的实现中 size_t 的类型为 unsigned longunsigned int

    当 i=0 时条件成立,--i 会将其转换为 4294967295UL = ULONG_MAX,因此循环中的测试条件 i &gt;= 0 永远不会为假。

    size_t 有一些无符号类型,我永远不会是负数。

    【讨论】:

      猜你喜欢
      • 2016-11-06
      • 2014-03-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-11
      相关资源
      最近更新 更多