delphi - instruccion - ¿Cuál es el comportamiento de shl y shr para operandos de tamaño no registrado?
instruccion add (3)
Esta pregunta se inspira en mis intentos de responder a otra pregunta: Convertir decimal / entero a binario: ¿cómo y por qué funciona de la manera en que lo hace?
La única documentación para los operadores de turnos bit a bit que puedo encontrar dice:
Las operaciones x shl y y x shr y desplazan el valor de x hacia la izquierda o la derecha en y bits, que (si x es un entero sin signo) es equivalente a multiplicar o dividir x por 2 ^ y; el resultado es del mismo tipo que x. Por ejemplo, si N almacena el valor 01101 (decimal 13), entonces N shl 1 devuelve 11010 (decimal 26). Tenga en cuenta que el valor de y se interpreta módulo el tamaño del tipo de x. Así, por ejemplo, si x es un número entero, x shl 40 se interpreta como x shl 8 porque un entero es 32 bits y 40 mod 32 es 8.
Considera este programa:
{$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.
He ejecutado esto tanto con XE3 como con XE5, para compiladores de Windows de 32 y 64 bits, y las salidas son consistentes, como se comentó en el código anterior.
Esperaba que (u8 shl 7) shr 7
se evaluara por completo en el contexto de un tipo de 8 bits. Entonces, cuando los bits se desplazan más allá del final de ese tipo de 8 bits, esos bits se pierden.
Mi pregunta es por qué el programa se comporta como lo hace.
Curiosamente, traduje el programa a C ++ y en mi 64 bit mingw 4.6.3 obtuve el mismo resultado.
#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;
}
La razón es tipo de promoción :
Un caso especial de conversión de tipo implícito es la promoción de tipo, donde el compilador expande automáticamente la representación binaria de objetos de tipo entero o de coma flotante. Las promociones se usan comúnmente con tipos más pequeños que el tipo nativo de la ALU de la plataforma objetivo antes de las operaciones aritméticas y lógicas para hacer posibles tales operaciones, o más eficientes si la ALU puede funcionar con más de un tipo. C y C ++ realizan dicha promoción para objetos de tipo booleano, carácter, carácter ancho, enumeración y entero corto que se promueven a int, y para objetos de tipo float, que se promueven como dobles. A diferencia de otras conversiones de tipo, las promociones nunca pierden precisión ni modifican el valor almacenado en el objeto.
Entonces en el siguiente código
var
u8: Byte;
begin
u8 := $ff;
Writeln((u8 shl 7) shr 7);
..
el valor u8
se promueve a 32 valores antes de shl
; para arreglar el resultado necesita una conversión de tipo explícita:
Writeln(Byte(u8 shl 7) shr 7);
Estándar C ++, Sección 4.5 Promociones integrales:
Un rvalue de tipo char, signed char, unsigned char, short int o unsigned short int se puede convertir a un rvalue de tipo int si int puede representar todos los valores del tipo de fuente; de lo contrario, el valor de origen r se puede convertir a un valor r de tipo unsigned int.
Para verificar si Delphi sigue la misma convención en el tipo de promoción, he escrito la siguiente aplicación:
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.
Así que creo que no debería haber diferencia entre Delphi y C ++ aquí.
Modifiqué tu prueba a
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.
y me dio este resultado
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
Entonces, internamente, los valores no se manejan en el tipo en que se declaran
Lo que sucede detrás de las escenas es realmente bastante interesante.
Dada la siguiente aplicación 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.
Se produce el siguiente ensamblaje (en 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
La regla, en lo que puedo entender es:
Regla
La estrechez del cambio que se realiza (8/16/32 bits) depende del tamaño del resultado del cambio, no del tamaño de las variables utilizadas en el cambio. En el caso original, no reserva una variable para mantener el resultado y, por lo tanto, Delphi elige un valor predeterminado (entero) para usted.
Cómo obtener el resultado esperado
En mi caso alterado, el resultado es de tamaño byte y, por lo tanto, los datos se cortan a ese tamaño.
Si modifica su caso para forzar el uso de bytes, se cumplen sus expectativas originales:
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