C# 为什么在移位32位值时只使用移位操作数的低位5位?(例如(UInt32)1<;<;33==2)

C# 为什么在移位32位值时只使用移位操作数的低位5位?(例如(UInt32)1<;<;33==2),c#,bit-shift,C#,Bit Shift,考虑以下代码: UInt32 val = 1; UInt32 shift31 = val << 31; // shift31 == 0x80000000 UInt32 shift32 = val << 32; // shift32 == 0x00000001 UInt32 shift33 = val << 33; // shift33 ==

考虑以下代码:

UInt32 val = 1;
UInt32 shift31 = val << 31;                    // shift31  == 0x80000000
UInt32 shift32 = val << 32;                    // shift32  == 0x00000001
UInt32 shift33 = val << 33;                    // shift33  == 0x00000002
UInt32 shift33a = (UInt32)((UInt64)val << 33); // shift33a == 0x00000000
我理解正在发生的事情(如中所述);在编译代码时,当移位32位值时,仅使用较低的5位。。。我很好奇为什么会发生这种情况

(shift33a的出现方式也让我觉得Reflector有点不太对劲,因为他们对IL的c#表示将编译成不同的东西)

问题:

  • 为什么只使用“要移位的值”的低5位
  • 如果“移位超过31位没有意义”,为什么没有警告
  • 这是向后兼容的事情吗(也就是说,这是程序员“期望”发生的事情)
  • 底层IL可以执行超过31位的移位(如
    L0010:ldc.i4.s 0x21
    ),但编译器正在修剪值,这一点是否正确

它基本上归结为x86处理算术移位操作码的方式:它只使用移位计数的底部5位。例如,请参见。在C/C++中,执行超过31位的位移位(对于32位整数)在技术上是未定义的行为,这符合C的理念“你不需要为你不需要的东西付费”。根据C99标准第6.5.7节第3段:

对每个操作数执行整数提升。结果的类型是提升的左操作数的类型。如果右操作数的值为负或为 大于或等于提升的左操作数的宽度,行为未定义

这允许编译器在x86上省略单个移位指令进行移位。在x86上,64位移位不能在一条指令中完成。它们使用/指令加上一些附加逻辑。在x86_64上,64位移位可以在一条指令中完成

例如,gcc 3.4.4以任意量(使用
-O3-fomit帧指针编译)发射以下64位左移位程序集:

uint64\u t l换档(uint64\u t x,int r)
{

return x我用C(gcc,linux)编写了这个简单的测试,得到了类似的结果。但有趣的是,用于过度移位的常量定义被转换为零,而不是环绕。它确实给出了关于这些的警告,所以至少有人认识到这是一个“不正确”的操作

#include <stdio.h>

unsigned int is0 = 1 << 31;
unsigned int is1 = 1 << 32;
unsigned int is2 = 1 << 33;

int main()
{
   unsigned int loopy = 0;
   int x = 0;
   printf("0x%08X\n", is0);
   printf("0x%08X\n", is1);
   printf("0x%08X\n", is2);


   for (x = 0; x < 35; ++x)
   {
      loopy = 1 << x;
      printf("%02d 0x%08X\n", x,loopy);
   }

   return 0;
}

Unit32以32位溢出,这是规范中定义的。您期望得到什么

CLR不使用溢出检测运算符(1)定义左移。如果您需要这种设施,您需要自己检查


(一)C#编译器可能会将其转换为long,但我不确定。

那么对shift33a的解释是什么?这是向后兼容的问题吗?我怀疑C#作为一种CLR语言,应该特别关注x68的行为。当然,它是唯一一个官方支持它的平台,但VM仍然不需要与现有处理器有太多的兼容性s、 我在ISO C99(草案)中没有看到任何关于位移位>31未定义的内容,我也不明白为什么会出现这种情况。C是(in)著名的字号不可知论者。IL似乎能够进行超过31位的移位(移位33A就是这种情况,它变成了
L0010:ldc.i4.s 0x21
,结果为0)如果你更进一步,问“为什么硬件会这样做?”,这里的答案似乎是:-本质上,硬件只需要获取相关的位就很方便了。甚至规范也没有给出“为什么”的细节.你可以试着问一下像Eric Lippert这样的C#编译器人员。@Johannes:是的,我只是想知道是否有人有用例或者什么东西来解释为什么这样做是有意义的:-/你永远不应该信任Reflector。我花了几个小时追踪bug,因为我信任输出。IL通常是最可靠的,但我甚至有过这样的情况结果不太好。改用ILDASM。@leppie:是的,这就是为什么我也发布了IL:-]好吧,直觉上,我认为左移1 32位会得到0(因为溢出),而不是1。它是未定义的,CLR可以做它想做的。我不制定规则…)是的,看看:如果“移位”操作数大于“待移位”操作数的宽度,则未指定结果。“我又仔细研究了你的问题,但我不明白你所说的5位是什么意思,你从哪里得到的?我的意思是,在确定值的移位量时,实际上只使用移位量的较低5位。因此,32位值上33位的移位实际上是1位的移位(因为33==00001的下五位)此代码的含义未定义。移位>=32位是未定义的。因此,您的程序无法可靠地显示任何内容。
L_0000: nop 
L_0001: ldc.i4.1 
L_0002: stloc.0 
L_0003: ldloc.0 
L_0004: ldc.i4.s 0x1f
L_0006: shl 
L_0007: stloc.1 
L_0008: ldloc.0 
L_0009: stloc.2 
L_000a: ldloc.0 
L_000b: ldc.i4.1 
L_000c: shl 
L_000d: stloc.3 
L_000e: ldloc.0 
L_000f: conv.u8 
L_0010: ldc.i4.s 0x21
L_0012: shl 
L_0013: conv.u4 
L_0014: stloc.s shift33a
uint64_t lshift(uint64_t x, int r)
{
  return x << r;
}

_lshift:
    movl    12(%esp), %ecx
    movl    4(%esp), %eax
    movl    8(%esp), %edx
    shldl   %cl,%eax, %edx
    sall    %cl, %eax
    testb   $32, %cl
    je      L5
    movl    %eax, %edx
    xorl    %eax, %eax
L5:
    ret
#include <stdio.h>

unsigned int is0 = 1 << 31;
unsigned int is1 = 1 << 32;
unsigned int is2 = 1 << 33;

int main()
{
   unsigned int loopy = 0;
   int x = 0;
   printf("0x%08X\n", is0);
   printf("0x%08X\n", is1);
   printf("0x%08X\n", is2);


   for (x = 0; x < 35; ++x)
   {
      loopy = 1 << x;
      printf("%02d 0x%08X\n", x,loopy);
   }

   return 0;
}
0x80000000
0x00000000
0x00000000
00 0x00000001
01 0x00000002
02 0x00000004
03 0x00000008
04 0x00000010
05 0x00000020
06 0x00000040
07 0x00000080
08 0x00000100
09 0x00000200
10 0x00000400
11 0x00000800
12 0x00001000
13 0x00002000
14 0x00004000
15 0x00008000
16 0x00010000
17 0x00020000
18 0x00040000
19 0x00080000
20 0x00100000
21 0x00200000
22 0x00400000
23 0x00800000
24 0x01000000
25 0x02000000
26 0x04000000
27 0x08000000
28 0x10000000
29 0x20000000
30 0x40000000
31 0x80000000
32 0x00000001
33 0x00000002
34 0x00000004