【问题标题】:How to perform rotate shift in C [duplicate]如何在C中执行旋转移位[重复]
【发布时间】:2011-02-19 14:21:40
【问题描述】:

如前所述,我有一个问题:如何在没有嵌入式程序集的情况下在 C 中执行旋转移位。更具体地说,如何旋转移位 32 位 int

我现在在long long int的帮助下解决这个问题,但是我觉得有点丑,想知道有没有更优雅的方法。

亲切的问候。

【问题讨论】:

    标签: c assembly


    【解决方案1】:

    (对未来读者的警告):维基百科的代码产生次优的 asm(gcc 包括一个分支或 cmov)。请参阅Best practices for circular shift (rotate) operations in C++ 了解高效的无 UB 旋转。


    来自Wikipedia

    unsigned int _rotl(unsigned int value, int shift) {
        if ((shift &= 31) == 0)
          return value;
        return (value << shift) | (value >> (32 - shift));
    }
    
    unsigned int _rotr(unsigned int value, int shift) {
        if ((shift &= 31) == 0)
          return value;
        return (value >> shift) | (value << (32 - shift));
    }
    

    【讨论】:

    • 如果目标有rotl指令,你认为最终汇编会使用它吗?如果不是(我怀疑),我想除了内联汇编没有其他方法了吗?
    • 我不知道维基百科是否正确,但是当我检查旋转操作时,如果简单地执行(value >> shift)似乎存在一些缺陷 | (值
    • 只有在被移位的变量是有符号的情况下,才应该对右移位进行符号扩展。如果您的“值”变量是有符号的,那么您需要将其转换为无符号以进行右移。
    • gcc -O3 (4.9.2) 编译这两个以使用旋转。不幸的是,它并没有消除条件!可读版本:goo.gl/ex5R7q。此处不可读版本:_rotr -> movl %esi, %ecx / movl %edi, %eax / andl $31, %ecx / rorl %cl, %eax / testl %ecx, %ecx / cmove %edi, %eax / ret(AMD64 ABI:%rdi 中的第一个参数,%rsi 中的第二个参数,%eax 中的返回)。即使使用-O0,gcc 也会将这对移位转换为旋转 insn。
    【解决方案2】:

    此答案与我在 Best-practices for compiler-friendly rotates 上发布的内容重复。

    详情请参阅my answer on another question

    在 C 中表达旋转以避免任何未定义行为的最编译器友好的方式似乎是 John Regehr 的实现:

    uint32_t rotl32 (uint32_t x, unsigned int n)
    {
      const unsigned int mask = (CHAR_BIT*sizeof(x)-1);
    
      assert ( (n<=mask) &&"rotate by type width or more");
      n &= mask;  // avoid undef behaviour with NDEBUG.  0 overhead for most types / compilers
      return (x<<n) | (x>>( (-n)&mask ));
    }
    

    适用于任何整数类型,而不仅仅是uint32_t,因此您可以制作多个版本。这个版本inlines to a single rol %cl, reg(或rol $imm8, reg)在x86上,因为编译器知道指令已经内置了掩码操作。

    我建议不要在操作数类型上进行模板化,因为当您将 16 位值存储在 int 临时值中时,您不想意外地旋转错误的宽度。特别是因为整数提升规则可以将涉及窄无符号类型的表达式的结果转换为 int

    确保对x 和返回值使用无符号类型,否则它不会是旋转。 (gcc 进行算术右移,移动符号位的副本而不是零,当你 OR 两个移位的值一起时会导致问题。)

    【讨论】:

    • @chux: 我可以放宽旋转计数的类型...gcc 在 x86 上生成更好的代码 n 是 32 或 64b 类型,而不是 16 或 8,而只是使用intunsigned 在那里可能没问题。不过,很好地抓住了我的 assert() 中的逻辑错误。
    • @chux:哦,我真傻。我在想你说的是nx,而不是nmask。在尝试了 64、32、16 和 8 位类型之后,我为 n 选择了 uint32_t,并发现 gcc 在最少使用 uint32_t n 的情况下发出了冗余的 AND 指令。
    • @chux:同意。 unsigned 在我查看 asm 输出的编译器/平台上已经是 32 位了。我更新了我的答案,感谢您帮助完善它。
    • 清理时间。现在比stackoverflow.com/a/776523/2410359更好了
    • @chux: 是的,正在研究它:) 我正在考虑将变量名称更改为 n = 正在旋转的值,c = 旋转计数。但是那样它就与 John Regehr 的原作不匹配,并且上面的一些 cmets 将是无稽之谈。但许多其他问题使用n 作为旋转值。
    【解决方案3】:

    虽然线程很旧,但我想在讨论中添加我的两分钱并提出我的问题解决方案。希望值得一看,但如果我错了,请纠正我。

    当我在寻找有效且安全的旋转方式时,我真的很惊讶没有真正的解决方案。我在这里找到了一些相关的线程: https://blog.regehr.org/archives/1063(安全、高效、便携的 C/C++ 轮播), Best practices for circular shift (rotate) operations in C++ 和维基百科风格(涉及分支但安全):

    uint32_t wikipedia_rotl(uint32_t value, int shift) {
        if ((shift &= 31) == 0)
          return value;
        return (value << shift) | (value >> (32 - shift));
    }
    

    经过一番思考后,我发现模除法符合标准,因为结果提示总是低于除数,它完全符合 shift

    ∀ x>=0, y: (x mod y)

    在我们的例子中,每个 (x % 32)

    #include <stdint.h>
    uint32_t rotl32b_i1m (uint32_t x, uint32_t shift)
        {
        shift %= 32;
        return (x<<shift) | (x>>(32-shift));
        }
    

    另外,mod() 将简化该过程,因为实际旋转,假设 100 位旋转 32 位完整 3 次,这基本上没有改变,然后是 4 位。那么计算 100%32==4 并旋转 4 位不是更好吗?无论如何,它需要单处理器操作并将其带到常量值的旋转加上一条指令,好的两条作为参数必须从堆栈中获取,但它仍然比使用 if() 像“wikipedia”方式进行分支更好。

    那么,你们怎么看?

    【讨论】:

    • 欢迎来到 Stack Overflow!这并不能真正回答问题。如果您正在寻找反馈,您可能希望以更完整的方式编写您的示例,并通过Code Review 寻求批评。请务必先阅读A guide to Code Review for Stack Overflow users,因为那里有些事情的处理方式不同!
    • shift %= 32 与 John Regehr 的 shift &amp;= 31 完全相同。以 2 为基数的模可以通过屏蔽高位来完成。 (编译器知道这一点,所以他们会以相同的方式编译您的版本,至少在启用优化时是如此。)除非您忘记将其应用于(32-shift),因此您的版本对于shift=0 具有 C 未定义行为(尽管在大多数编译器上它只会编译为旋转。)
    • 是的,实际上你是对的 - 我认为 shift=32 会发生什么,但没有分析 shift=0。同时,它会导致与 `shift&=31' 相同的未定义行为,这在 C/C++ 中正式取消两个版本的资格并强制使用“wikipedia”版本(检查条件 & 将产生分支)。我检查了 gcc,它使用 roll 生成相当安全的代码:# gcc -g -O2 -S rot321m1.c _rotl32b_i1m: pushl %ebp movl %esp, %ebp movl 8(%ebp), %eax movl 12(%ebp), %ecx andl $31, %ecx roll %cl, %eax leave ret // 与 John Regehr 的 shift&=31 版本相同
    • 错误:“n”未在此范围内声明。你的意思是n = shift吗?还是我错过了什么?
    • @AnnoyinC:是的,你是对的,应该是 shiftreturn (x&lt;&lt;shift) | (x&gt;&gt;(32-shift));.
    猜你喜欢
    • 2021-03-27
    • 2016-06-29
    • 1970-01-01
    • 1970-01-01
    • 2012-08-10
    • 2013-07-20
    • 2015-08-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多