对于非寄存器大小的操作数,shl和shr的行为是什么?

Dav*_*nan 12 delphi

这个问题的灵感来自于我尝试回答另一个问题:将十进制/整数转换为二进制 - 它是如何以及为什么以它的方式工作?

我能找到的按位移位运算符的唯一文档说:

操作x shl y和x shr y将x的值向左或向右移位y位,其中(如果x是无符号整数)等于x乘以或除以2 ^ y; 结果与x的类型相同.例如,如果N存储值01101(十进制13),则N shl 1返回11010(十进制26).请注意,y的值以x的类型的大小为基础进行解释.因此,例如,如果x是整数,则x shl 40被解释为x sh18,因为整数是32位而40 mod 32是8.

考虑这个程序:

{$APPTYPE CONSOLE}
program BitwiseShift;
var
  u8: Byte;
  u16: Word;
  u32: LongWord;
  u64: UInt64;
begin
  u8 := $ff;
  Writeln((u8 shl 7) shr 7);
  // expects: 1 actual: 255

  u16 := $ffff;
  Writeln((u16 shl 15) shr 15);
  // expects: 1 actual: 65535

  u32 := $ffffffff;
  Writeln((u32 shl 31) shr 31);
  // expects: 1 actual: 1

  u64 := $ffffffffffffffff;
  Writeln((u64 shl 63) shr 63);
  // expects: 1 actual: 1
end.
Run Code Online (Sandbox Code Playgroud)

对于32位和64位Windows编译器,我使用XE3和XE5运行它,并且输出是一致的,如上面的代码中所述.

我预计这(u8 shl 7) shr 7将完全在8位类型的上下文中进行评估.因此,当位移位超出该8位类型的末尾时,这些位将丢失.

我的问题是为什么程序的行为与它一样.


有趣的是,我将程序翻译成C++,并在我的64位mingw 4.6.3上获得了相同的输出.

#include <cstdint>
#include <iostream>

int main()
{
    uint8_t u8 = 0xff;
    std::cout << ((u8 << 7) >> 7) << std::endl;

    uint16_t u16 = 0xffff;
    std::cout << ((u16 << 15) >> 15) << std::endl;

    uint32_t u32 = 0xffffffff;
    std::cout << ((u32 << 31) >> 31) << std::endl;

    uint64_t u64 = 0xffffffffffffffff;
    std::cout << ((u64 << 63) >> 63) << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

klu*_*udg 11

原因是类型推广:

隐式类型转换的一个特例是类型提升,其中编译器自动扩展整数或浮点类型的对象的二进制表示.在算术和逻辑运算之前,促销通常使用小于目标平台ALU的本机类型的类型,以使这些操作成为可能,或者如果ALU可以使用多种类型,则更有效.C和C++对boolean,character,wide character,enumeration和short整数类型的对象执行此类提升,这些对象被提升为int,对于float类型的对象,它们被提升为double.与其他类型转换不同,促销永远不会丢失精度或修改存储在对象中的值.

所以在下面的代码中

var
  u8: Byte;

begin
  u8 := $ff;
  Writeln((u8 shl 7) shr 7);
..
Run Code Online (Sandbox Code Playgroud)

u8值之前被提升为32值shl; 要修复结果,您需要显式类型转换:

  Writeln(Byte(u8 shl 7) shr 7);
Run Code Online (Sandbox Code Playgroud)

C++标准,第4.5节整体促销:

如果int可以表示源类型的所有值,则可以将char,signed char,unsigned char,short int或unsigned short int类型的rvalue转换为int类型的rvalue.否则,源rvalue可以转换为unsigned int类型的rvalue.


要检查Delphi是否遵循类型提升中的相同约定,我编写了以下应用程序:

var
  u8: Byte;
  u16: Word;
  u32: LongWord;

procedure Test(Value: Integer); overload;
begin
  Writeln('Integer');
end;

procedure Test(Value: Cardinal); overload;
begin
  Writeln('Cardinal');
end;

begin
  u8 := $ff;
  Test(u8);     // 'Integer'
  u16 := $ffff;
  Test(u16);    // 'Integer'
  u32 := $ffffffff;
  Test(u32);    // 'Cardinal'
  Readln;
end.
Run Code Online (Sandbox Code Playgroud)

所以我相信Delphi和C++之间应该没有区别.


Sir*_*ufo 6

我修改了你的测试

procedure TestByte;
var
  u8 : Byte;
  LShift : Integer;
begin
  Writeln( 'Byte' );
  u8 := $FF;
  LShift := 7;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 15;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 31;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 63;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
end;

procedure TestWord;
var
  u8 : Word;
  LShift : Integer;
begin
  Writeln( 'Word' );
  u8 := $FF;
  LShift := 7;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 15;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 31;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 63;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
end;

procedure TestLongWord;
var
  u8 : LongWord;
  LShift : Integer;
begin
  Writeln( 'LongWord' );
  u8 := $FF;
  LShift := 7;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 15;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 31;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 63;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
end;

procedure TestUInt64;
var
  u8 : UInt64;
  LShift : Integer;
begin
  Writeln( 'UInt64' );
  u8 := $FF;
  LShift := 7;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 15;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 31;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
  LShift := 63;
  Writeln( IntToHex( u8, 16 ), '-', LShift : 2, ' ', IntToHex( u8 shl LShift, 16 ), ' ', IntToHex( ( u8 shl LShift ) shr LShift, 16 ) );
end;

begin
  TestByte;
  TestWord;
  TestLongWord;
  TestUInt64;
end.
Run Code Online (Sandbox Code Playgroud)

它给了我这个结果

Byte
00000000000000FF- 7 0000000000007F80 00000000000000FF
00000000000000FF-15 00000000007F8000 00000000000000FF
00000000000000FF-31 0000000080000000 0000000000000001
00000000000000FF-63 0000000080000000 0000000000000001
Word
00000000000000FF- 7 0000000000007F80 00000000000000FF
00000000000000FF-15 00000000007F8000 00000000000000FF
00000000000000FF-31 0000000080000000 0000000000000001
00000000000000FF-63 0000000080000000 0000000000000001
LongWord
00000000000000FF- 7 0000000000007F80 00000000000000FF
00000000000000FF-15 00000000007F8000 00000000000000FF
00000000000000FF-31 0000000080000000 0000000000000001
00000000000000FF-63 0000000080000000 0000000000000001
UInt64
00000000000000FF- 7 0000000000007F80 00000000000000FF
00000000000000FF-15 00000000007F8000 00000000000000FF
00000000000000FF-31 0000007F80000000 00000000000000FF
00000000000000FF-63 8000000000000000 0000000000000001

因此,在内部,值不会在声明它们的类型中处理