instrucciones instruccion ensamblador aritmeticas delphi

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