首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >对于非寄存器大小的操作数,shl和shr的行为是什么?

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

Stack Overflow用户
提问于 2014-01-26 10:49:59
回答 2查看 2.5K关注 0票数 12

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

我能找到的位移位操作符的唯一文档是:

运算x shl y和x shr y将x的值左或右移到y位,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。

考虑一下这个项目:

代码语言:javascript
复制
{$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.

我已经在32位和64位Windows编译器上使用XE3和XE5运行了这一功能,并且这些输出都是一致的,正如上面的代码中所注释的那样。

我预计(u8 shl 7) shr 7将完全在8位类型的上下文中进行评估。因此,当比特在8位类型的末尾移动时,这些比特就会丢失。

我的问题是为什么程序表现得像它一样。

有趣的是,我把程序翻译成了C++,在我的64位交汇上,4.6.3获得了相同的输出。

代码语言:javascript
复制
#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;
}
EN

回答 2

Stack Overflow用户

发布于 2014-01-26 12:56:05

原因是类型推广

隐式类型转换的一个特例是类型提升,其中编译器自动扩展整数或浮点类型对象的二进制表示。晋升通常用于在算术和逻辑操作之前小于目标平台的ALU的本地类型的类型,以便使这种操作成为可能,或者如果ALU可以与多个类型一起工作,则使用更高的效率。C和C++对提升为int的布尔、字符、宽字符、枚举和短整数类型的对象以及提升为双倍的浮点类型的对象执行这种提升。与其他类型转换不同,升级从不丢失精度或修改存储在对象中的值。

所以在下面的代码中

代码语言:javascript
复制
var
  u8: Byte;

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

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

代码语言:javascript
复制
  Writeln(Byte(u8 shl 7) shr 7);

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

如果int可以表示源类型的所有值,则可以将char、符号char、无符号char、空int或无符号短int的rvalue转换为int类型的rvalue;否则,可以将源rvalue转换为类型无符号int的rvalue。

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

代码语言:javascript
复制
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.

所以我认为德尔福和C++在这里应该没有什么区别。

票数 11
EN

Stack Overflow用户

发布于 2014-01-27 21:02:13

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

提供了以下Delphi应用程序:

代码语言:javascript
复制
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中)

代码语言:javascript
复制
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为您选择了一个默认值(整数)。

如何获得预期结果

在我修改的例子中,结果是字节大小的,因此数据被切割到这个大小。

如果您更改您的情况以强制使用字节,则您最初的期望将得到满足:

代码语言:javascript
复制
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
票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/21362455

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档