虽然@jlahd 的答案是正确的,但我会尝试简要说明logical shift right 和arithmetic shift right 之间的区别(可以找到另一个很好的区别图表here)。
请先阅读链接,如果您仍然感到困惑,请阅读以下内容:
两种不同右移的简要说明
现在,如果您将变量声明为int x = 8;,C 编译器就会知道这个数字是有符号的,并且当您使用这样的移位运算符时:
int x = 8;
int y = -8;
int shifted_x, shifted_y;
shifted_x = x >> 2; // After this operation shifted_x == 2
shifted_y = y >> 2; // After this operation shifted_y == -2
原因是右移表示除以2的幂。
现在,我很懒,所以让我在假设的机器上将int 设为 8 位,这样我就可以节省一些写作时间。在二进制 8 和 -8 中看起来像这样:
8 = 00001000
-8 = 11111000 ( invert and add 1 for complement 2 representation )
但在计算二进制数时,11111000 是十进制的 248。如果我们记得那个变量有一个符号,它只能表示 -8...
如果我们想要保留移位的良好属性,其中移位表示除以 2 的幂(这非常有用)并且我们现在想要有符号数,我们需要进行两种不同类型的右移因为
248 >> 1 = 124 = 01111100
-8 >> 1 = -4 = 11111100
// And for comparison
8 >> 1 = 4 = 00000100
我们可以看到,第一个移位在前面插入了一个 0,而第二个移位插入了一个 1。这是因为有符号数和无符号数之间的差异,在二进制补码表示中,除以 2 的幂时.
为了保持这种精确性,我们为有符号和无符号变量提供了两种不同的右移运算符。在汇编中,您可以显式声明您希望使用哪个,而在 C 中,编译器会根据声明的类型为您决定。
代码泛化
我会以稍微不同的方式编写代码,以使自己至少与平台无关。
#define ROTR(x,n) (((x) >> (n)) | ((x) << ((sizeof(x) * 8) - (n))))
#define ROTR(x,n) (((x) >> (n)) | ((x) << ((sizeof(x) * 8) - (n))))
这稍微好一点,但您仍然必须记住在使用此宏时保持变量无符号。我可以尝试像这样投射宏:
#define ROTR(x,n) (((size_t)(x) >> (n)) | ((size_t)(x) << ((sizeof(x) * 8) - (n))))
#define ROTR(x,n) (((size_t)(x) >> (n)) | ((size_t)(x) << ((sizeof(x) * 8) - (n))))
但现在我假设您永远不会尝试旋转大于size_t的整数...
为了摆脱可能是 1 或 0 的右移的高位,具体取决于编译器选择的移位类型,可以尝试以下操作(满足您的无强制转换要求):
#define ROTR(x,n) ((((x) >> (n)) & (~(0u) >> (n))) | ((x) << ((sizeof(x) * 8) - (n))))
#define ROTR(x,n) ((((x) >> (n)) & (~(0u) >> (n))) | ((x) << ((sizeof(x) * 8) - (n))))
但是对于 long 类型,它不会像预期的那样工作,因为 ~(0u) is of type unsigned int (first type which zero fits in the table) 并因此将我们限制为小于 sizeof(unsigned int) * 8 位的旋转。在这种情况下,我们可以使用~(0ul),但这使它成为unsigned long 类型,这种类型在您的平台上可能效率低下,如果您想传入long long,我们该怎么办?我们需要它与x 的类型相同,我们可以通过像~((x)^(x)) 这样的更神奇的表达式来实现它,但我们仍然需要将它转换为unsigned 版本,所以我们不要去那里。
@MattMcNabb 在 cmets 中还指出了另外两个问题:
我们的左移操作可能溢出。在对signed 类型进行操作时,即使在实践中它通常是相同的,我们也需要将左移操作中的 x 转换为 unsigned 类型,because it is undefined behavior when an arithmetic shift operation overflows(参见this answer's reference to the standard)。但是如果我们转换它,我们将再次需要为转换选择一个合适的类型,因为它的字节大小将作为我们可以旋转的上限......
我们假设字节有 8 位。 Which is not always the case, and we should use CHAR_BIT 而不是 8。
在这种情况下为什么要打扰?为什么不回到之前的解决方案,只使用the largest integer type, uintmax_t (C99),而不是size_t。但这现在意味着我们可能会在性能上受到惩罚,因为我们可能使用大于处理器字的整数,并且每个算术运算可能只涉及一条以上的汇编指令......不过这里是:
#define ROTR(x,n) (((uintmax_t)(x) >> (n)) | ((uintmax_t)(x) << ((sizeof(x) * CHAR_BIT) - (n))))
#define ROTR(x,n) (((uintmax_t)(x) >> (n)) | ((uintmax_t)(x) << ((sizeof(x) * CHAR_BIT) - (n))))
所以说真的,可能没有完美的方法来做到这一点(至少没有我能想到的)。您可以让它适用于所有类型,也可以通过仅处理等于或小于处理器字的事物来使其快速(消除long long 等)。但这很好而且通用,应该遵守标准......
如果您想要快速算法,那么您需要知道您正在为哪台机器编写代码,否则您将无法优化。
所以最后@jlahd 的解决方案会更好,而我的解决方案可能会帮助您使事情更通用(有代价)。