这个问题的灵感来自我尝试回答另一个问题: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.
对于32位和64位Windows编译器,我已经使用XE3和XE5运行了这个,并且outupts是一致的,如上面代码中所述。
我希望在{8}类型的上下文中完全评估(u8 shl 7) shr 7
。因此,当位移位超出该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;
}
答案 0 :(得分:10)
原因是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类型的rvalue,signed char,unsigned char,short int或 如果int可以表示,unsigned short int可以转换为int类型的rvalue 源类型的所有值;否则,源rvalue可以 转换为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 ++之间应该没有区别。
答案 1 :(得分:5)
我将您的测试修改为
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.
它给了我这个结果
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
因此,内部值不会以声明的类型处理
答案 2 :(得分:2)
幕后发生的事实上非常有趣。
鉴于以下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