右操作数为负时位移运算符的行为

Behavior of bitwise shift operators when right operand is negative

在 C 和 C++ 中,如果使用 >><<(右移和左移运算符)时右操作数为负,程序的行为是不确定的。 考虑以下程序:

#include <iostream>
int main()
{
    int s(9);
    std::cout<<(s<<-3);
}

g++ 给出以下警告:

[Warning] left shift count is negative [enabled by default]

MSVS 2010 给出以下警告:

warning c4293: '<<' : shift count negative or too big, undefined behavior

现在我很好奇 Java 和 C# 中发生了什么?

我试过以下程序

class left_shift_nagative
{
    public static void main(String args[])
    {
        int a=3;
        System.out.println(a<<-3);
        System.out.println(a>>-3);
    }
}

计划结果:

1610612736
0

轮到 C#了:

namespace left_shift_nagative
{
    class Program
    {
        static void Main(string[] args)
        {
            int s = 3;
            Console.WriteLine(s << -3);
            Console.WriteLine(s >> -3); 
        }
    }
}

输出:

1610612736
0

输出1610612736是怎么来的?这里发生了什么? Java 语言规范 (JLS) 和 C# 语言规范或标准对此有何评论?当 Java 和 C# 中给出负移位计数时,<< 和 >> 运算符如何工作?使用右移时如何得到输出 0?我真的很困惑。

我会回答 Java 部分(不能代表 C#,但可能是一样的)。

移位运算符 >><< 在 JLS section 15.19 中定义。引用(强调我的):

If the promoted type of the left-hand operand is int, then only the five lowest-order bits of the right-hand operand are used as the shift distance. It is as if the right-hand operand were subjected to a bitwise logical AND operator & (§15.22.1) with the mask value 0x1f (0b11111). The shift distance actually used is therefore always in the range 0 to 31, inclusive.

因此,当你移动 -3 时,就好像你移动 -3 & 0x1f 一样,即 29(只有右手操作数的最低五位被使用)。

  • 那么a << -3的结果就是2^29 * a;对于 a = 3,这是 1610612736
  • 那么a >> -3的结果就是floor(a / 2^29);对于 a = 3,这是 0

注意,当左操作数的提升类型是long,而不是int时,使用的掩码值是0x3f(只有6个最低位使用右侧的操作数)。

对于 C#

来自 C# 规范第 7.9 节 -

For the predefined operators, the number of bits to shift is computed as follows:

  • When the type of x is int or uint, the shift count is given by the low-order 5 bits of count. In other words, the shift count is computed from count & 0x1F
  • When the type of x is long or ulong, the shift count is given by the low-order 6 bits of count. In other words, the shift count is computed from count & 0x3F

这意味着忽略移位的符号。事实上,正如 Jon Skeet 在注释规范中指出的那样,由于规范定义的位掩码,以下代码将在 i == 31 之后换行。

for (int i = 0; i < 40; i++)
{
    Console.WriteLine(int.MaxValue >> i);
}