【发布时间】:2011-02-19 14:21:40
【问题描述】:
如前所述,我有一个问题:如何在没有嵌入式程序集的情况下在 C 中执行旋转移位。更具体地说,如何旋转移位 32 位 int。
我现在在long long int的帮助下解决这个问题,但是我觉得有点丑,想知道有没有更优雅的方法。
亲切的问候。
【问题讨论】:
如前所述,我有一个问题:如何在没有嵌入式程序集的情况下在 C 中执行旋转移位。更具体地说,如何旋转移位 32 位 int。
我现在在long long int的帮助下解决这个问题,但是我觉得有点丑,想知道有没有更优雅的方法。
亲切的问候。
【问题讨论】:
(对未来读者的警告):维基百科的代码产生次优的 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指令,你认为最终汇编会使用它吗?如果不是(我怀疑),我想除了内联汇编没有其他方法了吗?
_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。
此答案与我在 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 两个移位的值一起时会导致问题。)
【讨论】:
gcc 在 x86 上生成更好的代码 n 是 32 或 64b 类型,而不是 16 或 8,而只是使用int 或 unsigned 在那里可能没问题。不过,很好地抓住了我的 assert() 中的逻辑错误。
n 和x,而不是n 和mask。在尝试了 64、32、16 和 8 位类型之后,我为 n 选择了 uint32_t,并发现 gcc 在最少使用 uint32_t n 的情况下发出了冗余的 AND 指令。
unsigned 在我查看 asm 输出的编译器/平台上已经是 32 位了。我更新了我的答案,感谢您帮助完善它。
n = 正在旋转的值,c = 旋转计数。但是那样它就与 John Regehr 的原作不匹配,并且上面的一些 cmets 将是无稽之谈。但许多其他问题使用n 作为旋转值。
虽然线程很旧,但我想在讨论中添加我的两分钱并提出我的问题解决方案。希望值得一看,但如果我错了,请纠正我。
当我在寻找有效且安全的旋转方式时,我真的很惊讶没有真正的解决方案。我在这里找到了一些相关的线程: 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”方式进行分支更好。
那么,你们怎么看?
【讨论】:
shift %= 32 与 John Regehr 的 shift &= 31 完全相同。以 2 为基数的模可以通过屏蔽高位来完成。 (编译器知道这一点,所以他们会以相同的方式编译您的版本,至少在启用优化时是如此。)除非您忘记将其应用于(32-shift),因此您的版本对于shift=0 具有 C 未定义行为(尽管在大多数编译器上它只会编译为旋转。)
return (x<<shift) | (x>>(32-shift));.