【问题标题】:What is the behaviour of shl and shr for non register sized operands?对于非寄存器大小的操作数,shl 和 shr 的行为是什么?
【发布时间】:2014-01-26 10:49:59
【问题描述】:

这个问题的灵感来自于我尝试回答另一个问题:Converting decimal/integer to binary - how and why it works the way it does?

我能找到的唯一documentation 表示:

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 shl 8,因为整数是 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.

我已经使用 XE3 和 XE5 运行了这个,用于 32 位和 64 位 Windows 编译器,并且输出是一致的,如上面代码中所述。

我预计 (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;
}

【问题讨论】:

  • 我刚刚用 TP55 进行了测试,结果相似(寄存器大小为 16 而不是 32)。所以我猜按位运算默认使用(最大)寄存器大小变量。
  • 为什么不直接问Embarcadero?他们应该给你正确的答案
  • 来自关于整数类型的 Delphi 3 手册:Any byte-sized operand is converted to an intermediate word-sized operand that is compatible with both Smallint and Word before any arithmetic operation is performed.
  • @LURD 根据文档中的分类,这些是按位运算而不是算术运算:docwiki.embarcadero.com/RADStudio/XE5/Expressions_(Delphi)
  • *,/,div,mod,and,shl,shr,as 被归类为乘法运算符。这意味着编译器对它们应用相同的表达式语法。

标签: delphi


【解决方案1】:

原因是type promotion:

隐式类型转换的一个特例是类型提升,其中 编译器自动扩展二进制表示 整数或浮点类型的对象。促销活动很常见 与小于目标平台的本机类型的类型一起使用 ALU 之前的算术和逻辑运算,以使这样的 可能的操作,或者如果 ALU 可以使用更多 不止一种。 C 和 C++ 对以下对象执行此类提升 布尔值、字符、宽字符、枚举和短整数 提升为 int 的类型,对于 float 类型的对象, 被提升为双倍。与其他一些类型转换不同,促销 永远不会丢失精度或修改对象中存储的值。

所以在下面的代码中

var
  u8: Byte;

begin
  u8 := $ff;
  Writeln((u8 shl 7) shr 7);
..

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

  Writeln(Byte(u8 shl 7) shr 7);

C++ 标准,第 4.5 节整体提升:

char、signed char、unsigned char、short int 或 如果 int 可以表示,unsigned short int 可以转换为 int 类型的右值 源类型的所有值;否则,源右值可以是 转换为 unsigned int 类型的右值。


为了检查 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.

所以我相信这里的Delphi和C++应该没有区别。

【讨论】:

  • 没有。这就是你应该学习 C++ 来理解 Delphi 的情况。
  • 这对我有什么帮助。很明显,我可以看到正在发生的事情。我真的在找一些文档。
  • 所以我相信这里的Delphi和C++应该没有区别。C++标准并没有定义Delphi语言。
【解决方案2】:

我将你的测试修改为

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.

它给了我这个结果

字节 00000000000000FF- 7 0000000000007F80 00000000000000FF 00000000000000FF-15 00000000007F8000 00000000000000FF 00000000000000FF-31 0000000080000000 0000000000000001 00000000000000FF-63 0000000080000000 0000000000000001 单词 00000000000000FF- 7 0000000000007F80 00000000000000FF 00000000000000FF-15 00000000007F8000 00000000000000FF 00000000000000FF-31 0000000080000000 0000000000000001 00000000000000FF-63 0000000080000000 0000000000000001 长字 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

所以在内部,值不会按照它们声明的类型进行处理

【讨论】:

  • 问题中的代码表明它们没有以它们声明的类型进行处理。除了得到特殊处理的 32/64 位整数。看起来好像底层的 ISA 正在被反射回语言。
  • @DavidHeffernan 我猜u8 : byte; u8 := $ff; u8 := u8 shl 7; u8 := u8 shr 7; 应该会有预期的结果
  • 确实如此。或者更简洁的((u8 shl 7) and $ff) shr 7
【解决方案3】:

幕后发生的事情其实很有趣。

给定以下 Delphi 应用程序:

program BitwiseShift;
var
  u8: Byte;
begin
  //all in one go
  u8 := $ff;
  Writeln((u8 shl 7) shr 7);   
  // expects: 1 actual: 255

  //step by step
  u8 := $ff;
  u8:= u8 shl 7;
  u8:= u8 shr 7;
  WriteLn(u8);  
  // expects: 1 actual: 1
end.

生成以下程序集(在 XE2 中)

BitwiseShift.dpr.10: Writeln((u8 shl 7) shr 7);
004060D3 33D2             xor edx,edx
004060D5 8A1594AB4000     mov dl,[$0040ab94]
004060DB C1E207           shl edx,$07
004060DE C1EA07           shr edx,$07
004060E1 A114784000       mov eax,[$00407814]  <<--- The result is NOT a byte!!
004060E6 E895D6FFFF       call @Write0Long
004060EB E864D9FFFF       call @WriteLn
004060F0 E8A7CCFFFF       call @_IOTest
BitwiseShift.dpr.13: u8 := $ff;
004060F5 C60594AB4000FF   mov byte ptr [$0040ab94],$ff
BitwiseShift.dpr.14: u8:= u8 shl 7;
004060FC C02594AB400007   shl byte ptr [$0040ab94],$07
BitwiseShift.dpr.15: u8:= u8 shr 7;
00406103 33C0             xor eax,eax
00406105 A094AB4000       mov al,[$0040ab94]
0040610A C1E807           shr eax,$07
0040610D A294AB4000       mov [$0040ab94],al
BitwiseShift.dpr.16: WriteLn(u8);
00406112 33D2             xor edx,edx
00406114 8A1594AB4000     mov dl,[$0040ab94]
0040611A A114784000       mov eax,[$00407814]
0040611F E85CD6FFFF       call @Write0Long
00406124 E82BD9FFFF       call @WriteLn
00406129 E86ECCFFFF       call @_IOTest

据我所知,规则是:

规则

正在执行的移位的窄度(8/16/32 位)取决于 结果的大小,而不是变量的大小 在班次中使用。在原始情况下,您不保留变量 保存结果,因此 Delphi 选择一个默认值(整数) 你。

如何获得预期的结果
在我改变的情况下,结果是字节大小的,因此数据被切碎到那个大小。

如果你改变你的情况来强制使用字节,你最初的期望就会得到满足:

Writeln(byte(byte(u8 shl 7) shr 7));
// expects: 1 actual: 1

Project24.dpr.19: Writeln(byte(byte(u8 shl 7) shr 7));
00406135 8A1594AB4000     mov dl,[$0040ab94]
0040613B C1E207           shl edx,$07
0040613E 81E2FF000000     and edx,$000000ff
00406144 C1EA07           shr edx,$07
00406147 81E2FF000000     and edx,$000000ff
0040614D A114784000       mov eax,[$00407814]
00406152 E829D6FFFF       call @Write0Long
00406157 E8F8D8FFFF       call @WriteLn
0040615C E83BCCFFFF       call @_IOTest

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-07-17
    • 2021-11-10
    • 2021-02-14
    • 2017-10-07
    • 2014-02-04
    • 1970-01-01
    • 2013-07-05
    相关资源
    最近更新 更多