天天看點

Intel彙程式設計式設計-整數算術指令(中)

7.3  移位和循環移位的應用

7.3.1  多雙字移位

    要對擴充精度整數(長整數)進行移位操作,可把它劃分為位元組數組、字數組或雙字數組,然後再對該數組進行移位操作。在記憶體中存儲數字時通常采用的方式是最低位元組在最低的位址位置上(小尾順序)。下面的步驟以一個雙位元組數組為例,說明了如何把這樣的一個數組右移移位:

ArraySize = 3

.data

array DWORD ArraySize DUP(?)

1.把ESI的值設定為array的偏移。

2.把最高位置[ESI+8]處的雙字右移一位,最低位複制到進位标志中。

3.把[ESI+4]處的值右移一位,最高位自動以進位标志的值填充,最低位複制到進位标志中。

4.把[ESI+0]處的雙字右移一位,其最高位自動以進位标志填充,其最低位複制到進位标志中。

下圖顯示了數組的内容及使用ESI間接引用的表示:

Intel彙程式設計式設計-整數算術指令(中)

實作程式MultiShf.asm的代碼如下,程式中使用的是RCR指令,也可以使用SHRD指令:

.data

ArraySize = 3

array DWORD ArraySize DUP(99999999h) ;1001 1001...

.code

mov esi ,0

shr array[esi + 8]

rcr array[esi + 4]

rcr array[esi],1

...蛋疼,上面的那個我的卡了好幾分鐘才明白,我靠,一開始了解錯了。

7.3.2  二進制乘法

的二進制乘法指令(MUL和IMUL)相對于其他機器指令來說是比較緩慢的。彙編語言程式員通常會尋找更好的進行二進制乘法的方法,有時候移移位操作的優越性是顯而易見的。我們已經知道,在乘數是2的次幂的情況下,用SHL指令進行無符号數的乘法是相當高效的。無符号整數左移n位就相當于乘以2的n次幂。

例如,為了計算EAX乘以32

,就可以把36分解成(2的5cifang+2的2次方)

,然後用乘法配置設定率進行運算:

EAX * 32  =  EAX * (32 + 4)

         =  (EAX * 32) + (EAX * 4)

下圖描述了123*32的乘法過程,積為4428:

Intel彙程式設計式設計-整數算術指令(中)

乘數36的位2和位5是1,這些恰好是例子中的移位次數。

.code

mov  eax , 123

mov  ebx , eax

shl   eax , 5

shl   ebx , 2

add  eax , ebx

7.3.3  顯示二進制數的資料位

    一類常見的程式設計任務是要求把二進制整數轉換成ASCII二進制字元串以進行顯示。SHL指令這時就很有用了,因為SHL指令在每次操作數左移的時候,都會把最高位複制到進位标志中,下面的BinToAsc過程是一個簡單的實作。

;---------------------------------------------------------------

BinToAsc  PROC

;

;Converts 32-bit binary integer to ASCII binary.

;Receives:EAX = binary integer,ESIpoints to buffer

;Returns: buffer filled with ASCII binary digits

;---------------------------------------------------------------

     push  ecx

     push  esi

中資料位的數目

L1:  shl eax ,1           ;左移高位至進位标志中

選擇0作為預設數字

如果無進位,跳轉到L2

否則1送緩沖區

L2:   inc esi             ;下一個緩沖區位置

繼續循環,另外一位左移

 pop esi

 pop ecx

 ret

BinToAsc ENDP

7.4  乘法和除法指令  

和IMUL指令分别進行有符号整數和無符号整數的乘法操作。DIV指令進行無符号整數的除法操作,IDIV進行有符号整數的除法操作。

7.4.1  MUL指令

(無符号乘法)指令有三種格式:第一種将8位操作數與AL相乘;第二種将16位的操作數與AX相乘;第三種将32位的操作數與EAX相乘。乘數和被乘數大小必須相同,乘積的尺寸是乘數/被乘數大小的兩倍。三種格式都既接受寄存器操作數,也接收記憶體操作數,但是不接受立即數操作數。

MUL   r/m8

MUL   r/m16

MUL   r/m32

    指令中唯一的一個操作數是乘數。表7.2根據乘數大小的不同列出了被乘數和乘積,由于目的操作數(乘積)是乘數/被乘數大小的兩倍,是以不會發生溢出。如果積的高半部分不為0,就設定進位和溢出标志。由于進位标志通常用于服務号算術運算符,是以我們主要關注該标志。例如當AX與16位操作數相乘的時候,積存儲在DX:AX中。如果DX不為0,則進位标志置位。

Intel彙程式設計式設計-整數算術指令(中)

指令後要檢查進位标志的一個理由:有時我們需要知道乘積的高半部分是否可被安全的忽略。

MUL指令的例子

    下面的語句把AL和BL相乘,積在AX中,進位标志清零(CF=0),因為AH(乘積的高半部分)等于0:

mov  al ,5h

mov  bl ,10h

mul  bl         ;AX =50h ,CF = 0

下面的語句将16位數2000h和100h相乘,CF=1,因為乘積的高半部分DX不等于0;

.data

val1 WORD 2000h

val2 WORD 0100h

.code

mov  ax ,val1     ;AX = 2000h

mul   val2       ;DX:AX = 00200000h ,CF = 1

下面的語句将32位數12345h和1000h相乘得到一個64位的積,由EDX=0知CF=0;

mov  eax ,12345h

mov  ebx ,1000h   ;EDX:EAX = 000012345000h ,CF = 0

mul  ebx

7.4.2  IMUL指令

(有符号乘法)指令執行有符号整數的乘法運算,保留了成績的符号位。IMUL指令在IA-32指令集中由三種格式:單操作數、雙操作數和三操作數。在單操作數格式中,乘數和被乘數尺寸大小相同,乘積的大小是乘數/被乘數大小的兩倍(8086/8088處理器隻支援這種格式)。

單操作數格式:單操作數格式把乘積存儲在累加器(AX,DX:AX,EDX:EAX)中:

        IMUL  r/m8

        IMUL  r/m16

        IMUL  r/m32

    和MUL指令一樣,IMUL指令的單操作數格式中乘積的尺寸大小是的溢出不可能發生。如果乘積的高半部分不是低半部分的符号擴充,進位标志和溢出标志位,可以用該特點确定乘積的高半部分是否可以忽略。

雙操作數格式:雙操作數格式中成績存儲在第一個操作數中,第一個操作數必須是寄存器,第二個操作數可以是寄存器、記憶體書或立即數,下面是16位操作數的格式:

IMUL  r16,r/m16

IMUL  r16 ,imm8

IMUL  r16 ,imm16

    下面是32位操作數的格式,乘數必須是一個32位的寄存器、32位的記憶體操作數或立即數(8位或者32位):

IMUL r32 ,r/m32

IMUL r32, imm8

IMUL r32 ,imm32

    雙操作數格式會根據目的操作數的大小剪裁乘積。如果有效位丢失,則溢出标志和進位标志置位。使用雙操作數格式時,無比在執行完IMUL操作後檢查這些标志的值

三操作數格式:三操作數格式把乘積存儲在第一個操作數中,一個16位的寄存器可被一個8位或16位的立即數乘:

IMUL r16 ,r/m16 ,imm8

IMUL r16 ,r/m16 ,imm16

    一個32位的寄存器可被一個8位或32位的立即數乘:

IMUL r32 ,r/m32 ,imm8

IMUL r32 ,r/m32 ,imm32

    如果有效位丢失,則溢出标志和進位标志置位。使用三操作數格式時,務必在執行完IMUL操作後檢查這些标志的值。

無符号乘法:雙操作數和三操作數格式的IMUL指令也可用于進行無符号乘法。不過這樣做有一個缺陷:進位标志和溢出标志不能用來訓示乘積的高半部分是否為0.

7.4.3  乘法操作的基準(性能)測試

比較MUL,IMUL和移位做乘法指令的速度:

INCLUDE Irvine32.inc

.data

LOOP_COUNT = 0FFFFFFFFh

.data

intval DWORD 5

startTime DWORD ?

.code

main PROC

    call GetMseconds

    mov  startTime ,eax

mov  eax ,intval

call mult_by_MUL

call GetMseconds

sub  eax ,startTime

call WriteDec

main ENDP

mult_by_shifting PROC

;

;EAX乘以36,使用SHL指令,重複LOOP_COUNT次

    mov  ecx ,LOOP_COUNT

L1: push eax

    mov  ebx ,eax

shl  eax ,5

shl  ebx ,2

add  eax ,ebx

pop  eax

loop L1

ret

mult_by_shifting ENDP

mult_by_MUL PROC

;

;EAX乘以36,使用SHL指令,重複LOOP_COUNT次

    mov  ecx ,LOOP_COUNT

L1 :

    push eax

    mov  ebx ,36

mul  ebx

pop  eax

loop L1

ret

mult_by_MUL ENDP

END main

Intel彙程式設計式設計-整數算術指令(中)
Intel彙程式設計式設計-整數算術指令(中)

    按照書中作者的測試結果是,移位乘法

用時6.078s而mul則用了20.718s,但是我的測試結果是兩個差不多,但是這個并不和作者說的事情沖突,因為我目前使用的換将是vs2012+masm

vs會對代碼進行優化處理。

7.4.4  DIV指令

DIV(無符号除法)指令執行8位、16位和32位無符号整數的除法運算。指令中唯一的一個寄存器或記憶體操作數是除數,DIV的指令格式是:

DIV  r/m8

DIV  r/m16

DIV  r/m32

下表顯示了被除數、除數、商以及餘數之間的關系。

Intel彙程式設計式設計-整數算術指令(中)

DIV例子

下面的指令執行8位無符号數的除法(83h/2),上市401,餘數是1:

被除數

除數

        div   bl         ;AL = 41h ,AH = 01h

執行16位無符号除法(8003h/100h),商是80h,餘數是3。DX中存放的是被除數的高位,是以在執行DIV指令之前DX必須首先清零:

mov  dx ,0         ;清除被除數的高位

mov  ax ,8003h     ;被除數的地位

mov  cx ,100h      ;除數

div   cx           ;AX = 0080h ,DX = 003h

執行32位無符号除法,指令使用記憶體操作數作為除數:

        .data

        dividend  QWORD 0000000800300020h

        divisor   DWORD 00000100h

        .code

高雙子

低雙字

        div      divisor                ;EAX = 08003000h ,EDX = 00000020h

7.4.5  有符号整數除法

有符号整數除法和無符号幾乎完全相同,唯一不同:在進行除法操作之前,隐含的被除數北徐進行符号擴充。

符号擴充指令(CBW,CWD,CDQ)

有符号除法指令中的被除數在進行除法操作之前通常要進行符号擴充。Intel提供了三條符号擴充指令:CBW CWD CDQ。CBW指令(位元組符号擴充至字)擴充AL的符号位值AH中,保留了數字的符号。在下面的例子中(AL中的)9Bh和(AX中的)FF9Bf都等于-101:

.data

    byteVal  SBYTE  -101    ;9Bh

.code  

mov  al ,byteVal         ;AL  =  9Bh

cbw                    ;AX  =  FF9Bh

同理 CWD(字元号擴充至雙字)指令擴充AX的符号位至DX中:

...

CDQ(雙子位元組擴充至8位元組)指令擴充EAX的符号位至EDX中:

...

IDIV指令

IDIV(有符号除法)指令進行有符号整數的除法運算,使用的操作數格式與DIV指令相同。在進行8位除法之前,被除數(AX)必須進行符号擴充,餘數的符号和被除數總是相同。

例子:

.data

    byteVal  SBYTE  -48

.code

mov  al ,byteVal     ;被除數

cbw                ;擴充AL至AH

mov  bl ,5          ;除數

idiv   bl            ;AL = -9 ,AH = -3

.data

wordVal  SWORD  -5000

.code

mov  ax  ,wordVal      ;被除數的低半部分

cwd                   ;擴充AX值DX

mov  bx ,+256          ;除數

idiv   bx               ;商AX = -19

餘數DX = -136

.data

dwordVal  SDWORD  +50000

.code

mov    eax ,dwordVal     ;被除數的低半部分

cdq                     ;擴充EAX至EDX

mov    ebx ,-256         ;除數

idiv     ebx             ;EAX = -195 餘數 EDX =

+80 (注意此時餘數符号)

在執行DIV和IDIV指令後所有的算術狀态标志都是不确定的。

除法溢出

在除法操作産生的上太大,目的操作數無法容納的時候,就會導緻除法溢出,這會導緻CPU觸發一個中斷,目前程式将被終止。例如下面:

mov  ax ,1000h

mov  bl ,10h

div   bl       ;AL不能容納100h

Intel彙程式設計式設計-整數算術指令(中)

7.4.6  算術表達式的實作

var4 = (var1 + var2) * var3

用彙編實作下:

    mov  eax ,var1

    add  eax ,var2

    mul  var3       ;EAX = EAX * var3

無符号溢出?

    mov  var4 ,eax  

    jmp  next

tooBig:              ;顯示錯誤資訊

然後看下用C++編譯之後

vs反彙編會是什麼樣?