这个问题的灵感来源于我试图回答另一个问题:将十进制/整数转换为二进制-如何和为什么它的工作方式?
我能找到的位移位操作符的唯一文档是:
运算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。
考虑一下这个项目:
{$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获得了相同的输出。
#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;
}发布于 2014-01-26 12:56:05
原因是类型推广
隐式类型转换的一个特例是类型提升,其中编译器自动扩展整数或浮点类型对象的二进制表示。晋升通常用于在算术和逻辑操作之前小于目标平台的ALU的本地类型的类型,以便使这种操作成为可能,或者如果ALU可以与多个类型一起工作,则使用更高的效率。C和C++对提升为int的布尔、字符、宽字符、枚举和短整数类型的对象以及提升为双倍的浮点类型的对象执行这种提升。与其他类型转换不同,升级从不丢失精度或修改存储在对象中的值。
所以在下面的代码中
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节整体促销:
如果int可以表示源类型的所有值,则可以将char、符号char、无符号char、空int或无符号短int的rvalue转换为int类型的rvalue;否则,可以将源rvalue转换为类型无符号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.所以我认为德尔福和C++在这里应该没有什么区别。
发布于 2014-01-27 21:02:13
幕后发生的事情其实很有趣。
提供了以下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 @_IOTesthttps://stackoverflow.com/questions/21362455
复制相似问题