【问题标题】:Proper way to count down with unsigned使用无符号倒计时的正确方法
【发布时间】:2017-02-26 05:57:48
【问题描述】:

我正在阅读计算机系统上的卡内基梅隆大学幻灯片以进行测验。在slide 第49页:

无符号倒计时
使用无符号作为循环索引的正确方法

unsigned i; 
for (i = cnt-2; i < cnt; i--) 
a[i] += a[i+1]; 

更好

size_t i; 
for (i = cnt-2; i < cnt; i--) 
a[i] += a[i+1];   

我不明白为什么它不会是无限循环。我正在递减i,它是无符号的,所以它应该总是小于cnt。请解释一下。

【问题讨论】:

标签: c loops unsigned


【解决方案1】:

这个循环只是依赖于i 将递减到 0 以上的事实,这使其成为最大 uint 值。这打破了循环,因为现在i &lt; cnt == false

Overflowing of Unsigned Int:

无符号数不能溢出,而是使用 模数的性质。

C 和 C++ 标准都保证这种 uint 包装行为,但对于有符号整数没有定义。

【讨论】:

    【解决方案2】:

    循环的目标是从cnt-2 向下循环到0。达到写i &gt;= 0的效果。

    上一张幻灯片正确地讨论了为什么i &gt;= 0 的循环条件不起作用。无符号数总是大于或等于0,所以这样的条件是空洞的。 i &lt; cnt 的循环条件最终循环,直到 i 越过 0 并环绕。当您递减无符号 0 时,它变为 UINT_MAX(232 - 1 表示 32 位整数)。发生这种情况时,i &lt; cnt 保证为 false,循环终止。

    我不会写这样的循环。它在技术上是正确的,但风格很差。好的代码不仅是正确的,而且是可读的,所以其他人可以很容易地弄清楚它在做什么。

    【讨论】:

      【解决方案3】:

      它利用了当你将无符号整数 0 递减时发生的情况。这是一个简单的例子。

      unsigned cnt = 2;
      for (int i = 0; i < 5; i++) {
          printf("%u\n", cnt);
          cnt--;
      }
      

      这会产生...

      2
      1
      0
      4294967295
      4294967294
      

      无符号整数 0 - 1 变为 UINT_MAX。因此,与其寻找 -1,不如关注计数器何时变得大于其初始状态。

      稍微简化一下示例,以下是从 5 倒数到 0(不包括)的方法。

      unsigned i;
      unsigned cnt = 5;
      for (i = cnt-1; i < cnt; i--) {
          printf("%d\n", i);
      }
      

      打印出来的:

      4
      3
      2
      1
      0
      

      在最后一次迭代中i = UINT_MAX 保证大于cnt 所以i &lt; cnt 是假的。

      size_t“更好”,因为它是无符号的,并且与 C 中最大的东西一样大,因此您不必确保 cnti 的类型相同。

      【讨论】:

      • 产生与 unsigned i = cnt - 1; while (i--) printf("%u\n", i); 相同的迭代,但使用 post-decrement 运算符,该运算符允许在终止条件之前在循环主体内评估 i=0 while (0) 在下一次检查时(在后递减之前)。
      【解决方案4】:

      这似乎是实现相同事物的既定习语的另一种表达方式

      for (unsigned i = N; i != -1; --i) 
         ...;
      

      他们只是用更隐秘的i &lt; cnt 替换了更易读的i != -1。当0unsigned 域中递减时,它实际上回绕到UINT_MAX 值,比较等于-1(在无符号域中)并且大于或等于cnt。因此,i != -1i &lt; cnt 可以作为继续迭代的条件。

      他们为什么要专门这样做?显然是因为它们从cnt - 2 开始并且cnt 的值可以小于2,在这种情况下它们的条件确实可以正常工作(而i != -1 不能)。除了这种情况,没有理由让cnt 参与终止条件。有人可能会说,一个更好的主意是预先检查 cnt 的值,然后使用 i != -1 成语

      if (cnt >= 2)
        for (unsigned i = cnt - 2; i != -1; --i) 
           ...;
      

      注意,顺便说一句,只要知道i 的起始值是非负的,那么无论i 是有符号还是无符号,基于i != -1 条件的实现都有效。

      【讨论】:

      • 它确实i &lt; cnt 具有为cnt = 0 工作的优势。
      • 这个问题有很多正确的候选解决方案,但你的更详细。谢谢!
      • 我不相信这是支持的,error: comparison of constant -1 with expression of type 'uint8_t' (aka 'unsigned char') is always true
      【解决方案5】:

      我认为您对 int 和 unsigned int 数据类型感到困惑。这两个是不同的。在 int 数据类型(2 字节存储大小)中,您的范围从 -32,768 到 32,767 而在 unsigned int 数据类型(2 字节存储大小)。您的范围从 0 到 65,535 。 在上面提到的示例中,您使用的是 unsigned int 类型的变量 i。它将递减到 i=0,然后根据语义结束 for 循环。

      【讨论】:

        【解决方案6】:

        到目前为止我发现的向下计数循环的最佳选择是使用

        for(unsigned i=N; i-->0; ) {   }
        

        这将调用 i=N-1 ... 0 的循环体。这对有符号和无符号数据类型的工作方式相同,并且不依赖于任何溢出。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-03-23
          • 1970-01-01
          • 2011-06-20
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多