当前位置:  开发笔记 > 编程语言 > 正文

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

如何解决《为什么在移位32位值时仅使用移位操作数的低5位?(例如(UInt32)1<<33==2)》经验,为你挑选了1个好方法。

请考虑以下代码:

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的班次),因此它必须是预期的行为.

实际上被放到生成的程序集中的代码(或者至少是Reflector对代码的解释)是

 uint val = 1;
 uint shift31 = val << 0x1f;
 uint shift32 = val;
 uint shift33 = val << 1;
 uint shift33a = val << 0x21;  

IL(再次,使用Reflector)是

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

我理解发生了什么(在MSDN中描述); 当编译代码时,在移位32位值时只使用低5位......我很好奇为什么会发生这种情况.

(shift33a出来的方式也让我觉得Reflector有些不太正确,因为他们的IL的c#表示会编译成不同的东西)

问题:

为什么只使用"要转移的值"的低5位?

如果"移位超过31位没有意义",为什么没有警告?

这是一个向后兼容的事情(即这是程序员"期望"发生的事情)吗?

我是否正确,底层IL可以进行超过31位的移位(如L_0010: ldc.i4.s 0x21),但编译器正在修改值?

Adam Rosenfi.. 9

它基本上归结为x86处理算术移位操作码的方式:它只使用移位计数的底部5位.例如,请参阅80386编程指南.在C/C++中,技术上未定义的行为是将位移超过31位(对于32位整数),采用C语言"你不为你不需要的东西买单".根据C99标准第6.5.7节第3段:

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

这允许编译器在x86上省略单个移位指令以进行移位.在x86上的一条指令中无法进行64位移位.他们使用SHLD/SHRD指令以及一些额外的逻辑.在x86_64上,可以在一条指令中完成64位移位.

例如,gcc 3.4.4为64位左移发出以下程序集任意数量(编译-O3 -fomit-frame-pointer):

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

现在,我对C#不太熟悉,但我猜它有类似的理念 - 设计语言以尽可能高效地实现它.通过指定移位操作仅使用移位计数的底部5/6位,它允许JIT编译器尽可能最佳地编译移位.32位移位以及64位系统上的64位移位可以将JIT编译为单个操作码.

如果将C#移植到对其本机移位操作码具有不同行为的平台,那么这实际上会产生额外的性能损失 - JIT编译器必须确保标准得到尊重,因此它必须添加额外的逻辑确保仅使用移位计数的底部5/6位.



1> Adam Rosenfi..:

它基本上归结为x86处理算术移位操作码的方式:它只使用移位计数的底部5位.例如,请参阅80386编程指南.在C/C++中,技术上未定义的行为是将位移超过31位(对于32位整数),采用C语言"你不为你不需要的东西买单".根据C99标准第6.5.7节第3段:

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

这允许编译器在x86上省略单个移位指令以进行移位.在x86上的一条指令中无法进行64位移位.他们使用SHLD/SHRD指令以及一些额外的逻辑.在x86_64上,可以在一条指令中完成64位移位.

例如,gcc 3.4.4为64位左移发出以下程序集任意数量(编译-O3 -fomit-frame-pointer):

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

现在,我对C#不太熟悉,但我猜它有类似的理念 - 设计语言以尽可能高效地实现它.通过指定移位操作仅使用移位计数的底部5/6位,它允许JIT编译器尽可能最佳地编译移位.32位移位以及64位系统上的64位移位可以将JIT编译为单个操作码.

如果将C#移植到对其本机移位操作码具有不同行为的平台,那么这实际上会产生额外的性能损失 - JIT编译器必须确保标准得到尊重,因此它必须添加额外的逻辑确保仅使用移位计数的底部5/6位.

推荐阅读
360691894_8a5c48
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有