【问题标题】:Unexpected C/C++ bitwise shift operators outcome意外的 C/C++ 位移位运算符结果
【发布时间】:2012-04-09 06:32:32
【问题描述】:

我觉得我要疯了。

我有一段代码需要创建一个(无符号)整数,并将N 后续位设置为 1。确切地说,我有一个位掩码,在某些情况下,我想将其设置为实心范围。

我有以下功能:

void MaskAddRange(UINT& mask, UINT first, UINT count)
{
    mask |= ((1 << count) - 1) << first;
}

简单来说:1 &lt;&lt; count 二进制表示为100...000(零的个数为count),从这样的数字中减去1 得到011...111,然后我们只需将其左移first .

当满足以下明显限制时,以上应该会产生正确的结果:

first + count &lt;= sizeof(UINT)*8 = 32

请注意,它应该也适用于“极端”情况。

  • 如果count = 0 我们有(1 &lt;&lt; count) = 1,因此有((1 &lt;&lt; count) - 1) = 0
  • 如果count = 32 我们有(1 &lt;&lt; count) = 0,因为前导位溢出,并且根据 C/C++ 规则,按位移位运算符不是循环的。然后((1 &lt;&lt; count) - 1) = -1(所有位设置)。

然而,事实证明,对于count = 32,该公式无法按预期工作。发现:

UINT n = 32;
UINT x = 1 << n;
// the value of x is 1

此外,我使用的是 MSVC2005 IDE。当我在调试器中评估上述表达式时,结果为 0。但是,当我越过上述行时,x 的值为 1。通过反汇编程序锁定,我们看到以下内容:

mov eax,1 
mov ecx,dword ptr [ebp-0Ch] // ecx = n
shl eax,cl                  // eax <<= LOBYTE(ecx)
mov dword ptr [ebp-18h],eax // n = ecx

确实没有什么神奇之处,编译器只是使用了shl 指令。然后似乎shl 没有做我期望它应该做的事情。要么 CPU 决定忽略这条指令,要么以 32 为模处理移位,或者什么都不做。

我的问题是:

  • shl/shr 指令的正确行为是什么?
  • 是否有控制移位指令的 CPU 标志?
  • 这符合 C/C++ 标准吗?

提前致谢

编辑:

感谢您的回答。我意识到(1)shl/shr 确实将操作数模 32(或 & 0x1F)和(2)C/C++ 标准将移位超过 31 位视为未定义行为。

那么我还有一个问题。我怎样才能重写我的“屏蔽”表达式来涵盖这种极端情况。它应该没有分支(if?)。最简单的表达方式是什么?

【问题讨论】:

  • 没有“C/C++”这样的语言。您已将问题标记为 C,但您的代码之一 sn-ps 使用符号 UINT&amp; mask,该符号仅存在于 C++ 中。
  • 我想到了两件事:sizeof(UINT) 是什么,并确保 1 在 shl'ing 时也是 UINT。
  • 顺便回答你的最后一个问题:“这符合 C/C++ 标准吗?”:C 标准说,“如果右操作数的值为负数或大于或等于提升的左操作数的宽度,行为未定义”,并且 C++ 标准说,如果右操作数为负数,或者大于或等于提升的左操作数的位长度,则行为未定义。所以在任何一种语言中,当你这样做时,系统绝对可以做它想做的任何事情;它可以终止程序,或者向你的老板发送愤怒的电子邮件——任何事情

标签: c bit-manipulation


【解决方案1】:

unsigned int 类型为 32 位宽时,1U &lt;&lt; 32 在 C 和 C++ 中是未定义的行为。

(C11, 6.5.7p3) "如果右操作数的值为负数或大于等于提升的左操作数的宽度,则行为未定义"

(C++11, 5.8p1) “如果右操作数为负数,或者大于或等于提升的左操作数的位长度,则行为未定义。”

【讨论】:

    【解决方案2】:

    在 C 和 C++ 中,与您要移位的整数类型相比,移位与整数类型一样多或更多的位是 未定义。在 x86 和 x86_64 上,移位指令的移位量确实以 32 为模(或任何操作数大小)。但是,除非您的编译器在其文档中明确保证,否则您不能依赖编译器从 C 或 C++ &gt;&gt;/&lt;&lt; 操作生成此模行为。

    【讨论】:

      【解决方案3】:

      我认为表达式1 &lt;&lt; 321 &lt;&lt; 0 相同。 IA-32指令集参考说移位指令的计数操作数被屏蔽为5位。

      IA-32架构的指令集参考可以在here找到。

      为了解决“极端”的情况,我只能想出以下代码(可能是错误的),可能有点尴尬:

      void MaskAddRange(UINT *mask, UINT first, UINT count) {
          int count2 = ((count & 0x20) >> 5);
          int count1 = count - count2;
          *mask |= (((1 << count1) << count2) - 1) << first;
      }
      

      基本思想是拆分移位操作,使每个移位计数不超过31。 显然,上面的代码假设计数在 0..32 的范围内,所以它不是很健壮。

      【讨论】:

        【解决方案4】:

        如果我理解了要求,你想要一个无符号整数,设置前 N 位?

        有几种方法可以获得您想要的结果(我认为)。 编辑: 我担心这不是很健壮,并且会在 n>32 内失败:

        uint32_t set_top_n(uint32 n)
        {
            static uint32_t value[33] = { ~0xFFFFFFFF, ~0x7FFFFFFF, ~0x3FFFFFFF, ~0x1FFFFFFF,
                                          ~0x0FFFFFFF, ~0x07FFFFFF, ~0x03FFFFFF, ~0x01FFFFFF,
                                          ~0x00FFFFFF, ~0x007FFFFF, ~0x003FFFFF, ~0x001FFFFF,
                                          // you get the idea
                                          0xFFFFFFFF
                                          };
            return value[n & 0x3f];
        }
        

        这应该很快,因为它只有 132 字节的数据。

        为了使它健壮,我要么将所有值扩展到 63,要么使其有条件,在这种情况下,可以使用原始位掩码的一个版本 + 32 案例来完成。即

        【讨论】:

          【解决方案5】:

          我的 32 美分:

          #include <limits.h>
          
          #define INT_BIT     (CHAR_BIT * sizeof(int))
          
          unsigned int set_bit_range(unsigned int n, int frm, int cnt)
          {
                  return n | ((~0u >> (INT_BIT - cnt)) << frm);
          }
          

          列表 1。

          具有虚假/半圆形结果的安全版本可能是:

          unsigned int set_bit_range(unsigned int n, int f, int c)
          {
                  return n | (~0u >> (c > INT_BIT ? 0 : INT_BIT - c)) << (f % INT_BIT);
          }
          

          清单 2.

          在没有分支或局部变量的情况下执行此操作可能类似于:

          return n | (~0u >> ((INT_BIT - c) % INT_BIT)) << (f % INT_BIT);
          

          清单 3.

          List 2List 3 只要from 小于INT_BIT 并且>= 0,这将给出“正确”的结果。即: p>

          ./bs 1761 26 810
          Setting bits from 26 count 810 in 1761 -- of 32 bits
          Trying to set bits out of range, set bits from 26 to 836 in 32 sized range
          x = ~0u       =  1111 1111 1111 1111 1111 1111 1111 1111
          
          Unsafe version:
          x = x >> -778 =  0000 0000 0000 0000 0000 0011 1111 1111
          x = x <<  26  =  1111 1100 0000 0000 0000 0000 0000 0000
          x v1 Result   =  1111 1100 0000 0000 0000 0110 1110 0001
          Original:        0000 0000 0000 0000 0000 0110 1110 0001    
          
          Safe version, branching:
          x = x >>   0  =  1111 1111 1111 1111 1111 1111 1111 1111
          x = x <<  26  =  1111 1100 0000 0000 0000 0000 0000 0000
          x v2 Result   =  1111 1100 0000 0000 0000 0110 1110 0001
          Original:        0000 0000 0000 0000 0000 0110 1110 0001    
          
          Safe version, modulo:
          x = x >>  22  =  0000 0000 0000 0000 0000 0011 1111 1111
          x = x <<  26  =  1111 1100 0000 0000 0000 0000 0000 0000
          x v3 Result   =  1111 1100 0000 0000 0000 0110 1110 0001
          Original:        0000 0000 0000 0000 0000 0110 1110 0001
          

          【讨论】:

            【解决方案6】:

            您可以通过将移位操作分成两步来避免未定义的行为,第一步是 (count - 1) 位,第二步是多 1 位。但是,如果计数为零,则需要特别注意:

            void MaskAddRange(UINT& mask, UINT first, UINT count)
            {
              if (count == 0) return;
              mask |= ((1 << (count - 1) << 1) - 1) << first;
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2022-01-11
              • 2011-02-02
              • 2013-08-28
              • 1970-01-01
              • 2012-04-03
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多