极客算法

汇编语言02 - 加减乘除

2024-05-23
OS

字节序

读成(indian), 和内存中的二进制有关,主要处理二进制data的时候

the most significant bit在左(big)或者在右(little)

dword 00000004
big_endian_memory = 00 00 00 04  // IBM mainframes, RISC processors and Motorola processors
little_endian_memory = 04 00 00 00 //Intel-based processors
  1. 当二进制数据在不同的计算机之间传输时(无论是通过文件还是网络),可能会出现字节序(byte order)不同的问题
  2. 当二进制数据被写入内存作为多字节整数时,可能会出现字节序不同的问题。如果写入数据的计算机和读取数据的计算机的字节序不同,那么读取数据的结果可能会与写入数据的结果不同

整数表示

最高位,作为符号位表示正负数,0为正数,1为负数

符号表示

符号位+数的绝对值表示,这样1个byte可表示的范围[-127, 127]

有一个问题,有两个0,一正一负

反码

就是每个bit翻转一下,也可以看成(1 - 旧数)

+56 = bin(0 0111000)
-56 = bin(1 1000111)

+0 = bin(0 0000000)
-0 = bin(1 1111111)

+56 = hex(38)
-56 = hex(C7)  // F - 3 = C, F - 8 = 7

补码

现代计算机都是这么表示的,即反码+1

有个注意是0, 取反+1之后,高位进位是被忽略的

0 = 00000000
    11111111 
  +        1 
  c 00000000

这样表示,就只有一个0了

存储伸缩

CPU和汇编语言都不知道,具体byte到底表示什么, 怎样读取取决于具体指令(数据类型告诉具体使用哪个指令)

缩小

规则是被截断的部分,对于数字的表示不重要

  1. 对于无符号正数,被截断的部分应该都是0
  2. 对于符号数,被截断的部分全是1或0,且剩余部分符号相同
mov    ax, 0034h      ; ax = 52 (stored in 16 bits)
mov    cl, al         ; cl = lower 8-bits of ax

放大

  1. 对于无符号数,新增的部分都是0
  2. 对于符号数,新增的部分都为符号位

无符号

// AX = AH + AL
mov    ah, 0   ; zero out upper 8-bits

movzx  eax, ax   ; extends ax into eax
movzx  eax, al   ; extends al into eax
movzx  ax, al    ; extends al into ax
movzx  ebx, ax   ; extends ax into ebx

有符号

CBW  ; (Byte->Word) extends the AL register into AX
CWD  ; (Word -> Double Word) extends AX into DX:AX, 8086没有32位寄存器,两个合并来用

// 80386
CWDE  ; (Word -> Double Word) extends AX into EAX
CDQ   ; (Double World -> Quad Word) extend EAX into EDX:EAX, 64位 
MOVSX ; movzx的有符号版本

C代码

类型转换

unsigned char uchar = 0xFF;
signed char schar = 0xFF;
int a = (int) uchar; / a = 255 (0x000000FF) 使用MOVZX /
int b = (int ) schar ; / b = 1 (0xFFFFFFFF) 使用MOVSX /

int fgetc( FILE * );返回int, 函数会遇到两种情况,读取字符串转成int(000000xx), EOF=-1(FFFFFFFF)

char ch;
while( (ch = fgetc(fp)) != EOF ) {
    / do something with ch / 
}

// 首先 000000FF, FFFFFFFF 的结果都是FF

// 若char无符号,EOF=(FFFFFFFF)=CHAR(FF)=INT(000000FF), 死循环
// 若char有符号,EOF=(FFFFFFFF)=CHAR(FF)=INT(FFFFFFFF), 有两种跳出条件000000FF和EOF(FFFFFFFF)

运算指令

加减法

在Add和sub指令中,FLAGS中有两个标志位,overflowcarry

overflow: 运算结果过大,无法放到目标位置 carry: 加法中的进位,减法中的借位

由于二进制的表示,add和sub可以对有无符号数操作

  002C      44
+ FFFF  +  (-1)
-------------------
  002B      43

乘法

mul source, source 是内存或者寄存器

  • source(8-bit) 结果AX = Source * AL
  • source(16-bit) 结果DX:AX = Source * AX
  • source(32-bit) 结果EDX:EAX = Source * EAX
imul source

imul dest, source1 ; 只有乘法有
imul dest, source1, source2 ; 只有乘法有

除法

除法类似,DIVIDIV

div source

  • source(8-bit) 结果AL = AX / source, AH = remainder
  • source(16-bit) 结果AX = DX:AX / source, DX = remainder
  • source(32-bit) 结果EAX = EDX:EAX / source, EDX = remainder

NEG

neg operand, 计算补码支持(8,16,32-bit register和mem), 看起来就是正负数转换

代码

segment .data
;
; Output strings
;
prompt          db    "Enter a number: ", 0
square_msg      db    "Square of input is ", 0
cube_msg        db    "Cube of input is ", 0
cube25_msg      db    "Cube of input times 25 is ", 0
quot_msg        db    "Quotient of cube/100 is ", 0
rem_msg         db    "Remainder of cube/100 is ", 0
neg_msg         db    "The negation of the remainder is ", 0

segment .bss
input   resd 1


segment .text
        global  asm_main
asm_main:
        enter   0,0               ; setup routine
        pusha

        mov     eax, prompt
        call    print_string

        call    read_int
        mov     [input], eax

        imul    eax               ; edx:eax = eax * eax
        mov     ebx, eax          ; save answer in ebx
        mov     eax, square_msg
        call    print_string
        mov     eax, ebx
        call    print_int
        call    print_nl

        mov     ebx, eax
        imul    ebx, [input]      ; ebx *= [input]
        mov     eax, cube_msg
        call    print_string
        mov     eax, ebx
        call    print_int
        call    print_nl

        imul    ecx, ebx, 25      ; ecx = ebx*25
        mov     eax, cube25_msg
        call    print_string
        mov     eax, ecx
        call    print_int
        call    print_nl

        mov     eax, ebx
        cdq                       ; initialize edx by sign extension
        mov     ecx, 100          ; can't divide by immediate value
        idiv    ecx               ; edx:eax / ecx
        mov     ecx, eax          ; save quotient into ecx
        mov     eax, quot_msg
        call    print_string
        mov     eax, ecx
        call    print_int
        call    print_nl
        mov     eax, rem_msg
        call    print_string
        mov     eax, edx
        call    print_int
        call    print_nl
        
        neg     edx               ; negate the remainder
        mov     eax, neg_msg
        call    print_string
        mov     eax, edx
        call    print_int
        call    print_nl

        popa
        mov     eax, 0            ; return back to C
        leave                     
        ret

执行结果

root@vultr:~/pcasm/examples/linux# ./math
Enter a number: 4
Square of input is 16
Cube of input is 64
Cube of input times 25 is 1600
Quotient of cube/100 is 0
Remainder of cube/100 is 64
The negation of the remainder is -64

大数加减

大数的加减法,可以将大数分解成小部分,进行运算

ADC ; operand1 = operand1 + carry flag + operand2
SBB ; operand1 = operand1 - carry flag - operand2
CLC ; carry flag = 0

例如64位,EDX:EAX + EBX:ECX

add    eax, ecx       ; add lower 32-bits
adc    edx, ebx       ; add upper 32-bits and carry from previous sum

sub    eax, ecx       ; subtract lower 32-bits
sbb    edx, ebx       ; subtract upper 32-bits and borrow

更大的数可以使用for循环

–END–


评论

内容:
其他: