Ch 24 FPU (3) 指數

這一章,小木偶將介紹 8087 的重軸戲,超越指令,控制字組,最後發展出幾個有關計算指數的副程式。

超越指令

所謂超越指令是指複雜的函數運算的指令。8087 有五個超越指令,一個用於指數,兩個用於自然對數,另外兩個用於三角函數的計算。對於 8087 的超越指令所輸入的引數和數學上的引數不見得相同,例如求 2 的 X 次方,在數學上 X 可以是任意值,但用 8087 計算時 X 卻只能在 0 到 0.5 之間,如果要求 2 的任意數次方得用程式來計算。假如在 8087 程式中的引數超過這個範圍,會引起錯誤但是 8087 卻沒有檢查機制,所以要小心使用。

F2XM1 指令

這個指令是用來計算 2ST-1 然後將結果存回 ST 裏。F2XM1 裏的 X 表示 ST 暫存器,M 表示減法之意,它的語法就是

F2XM1

不含任何運算元。這個指令運算之前還有一個限制,那就是 X 必須是在 0 到 0.5 之間的實數才行,可以等於 0 或 0.5;在 Pentium 等級及其以上 (註一),X 可以擴充到在 0 到 1 之間。這個指令之所以要減一的目的是如果 X 很小,則 2X 會很接近一,減去一可增加有效數。

FYL2X 指令

這個指令是用來計算 ST(1)*Log2 ST,這個指令會先彈出 ST 然後以計算的結果取代 ST 暫存器。這個指令的限制是 ST 必須為正數。

FYL2XP1 指令

這個指令是計算 ST*Log2( ST(1)+1 ),這個指令會先彈出 ST 然後以計算的結果取代 ST 暫存器,ST(1) 必須是大於零且小於二分之根號二的數。這個指令在 Log 後的數很接近一時,比 FYL2X 有較好的準確度。

FPTAN 指令

這個指令用來計算 tan ST 之結果,而計算結果是以分數 Y/X 的形式存入堆疊,計算後先把 tan ST 之值推入堆疊(當作 Y 值),再把 1 (當作 X 值)推入堆疊,換句話說最後的結果是 ST(1) 為 tan ST,ST 為 1。在 8087 等級的 FPU 運算時,計算前 ST 必須是 0 到四分之圓周率的徑度;如果在 Pentium 等級及其以上的 CPU,除了計算前 ST 須以徑度表示外,似乎沒有範圍限制。

FPATAN 指令

這個指令是用來計算 arctan ( ST(1)/ST ) 的,然後把計算結果以徑度表示存入 ST。整個計算過程是先彈出堆疊頂當做分母(X),再彈出新的堆疊頂( 也就是原來的 ST(1) )當做分子,計算 Y/X 之反正切函數,再把計算結果存回堆疊頂。如果是在 8087 FPU 上運算,計算前 ST 與 ST(1) 必須為正值,而在 Pentium 及其以上的 CPU 則無此限制。

在 80387 等級及其以上的 FPU 還提供的更多的超越函數,小木偶在下面介紹。

FSIN 指令

這是用來計算堆疊頂的正弦函數 (sin),再把結果推入堆疊頂,計算前堆疊頂沒有範圍的限制,但要使用徑度,80387 等級及其以上的 FPU 才提供這個指令。

FCOS 指令

這是用來計算堆疊頂的餘弦函數 (cos),再把結果推入堆疊頂,計算前堆疊頂沒有範圍的限制,但要使用徑度,80387 等級及其以上的 FPU 才提供這個指令。

FSINCOS 指令

這個指令只有在 80387 等級及其以上的 FPU 才提供,它會彈出堆疊頂然後計算 sin ST 與 cos ST 之值,然後把 sin ST 之結果推入堆疊暫存器,再把 con ST 之結果推入堆疊暫存器,所以堆疊頂為餘弦值, ST(1) 為正弦值。


用 8087 計算 2x 的副程式

原理

8087 指令裏有兩個有關 2x 的指令,FSCALE 與 F2XM1,但是前者只能用在 x 值是在 -32768 和 32768 之間的整數,而後者只能用在 x 是在 0 和 0.5 之間的實數,所以假如要計算 2 的任意次方,必須另寫一個副程式才行,而且還要利用到數學上的原理:

2a+b=2a*2b

應用上述數學原理,我們可以把任意實數分成整數部份(a)與小數部份(b),但是考慮到 F2XM1 只能接受在 0 和 0.5 之間的實數為指數,以及指數為負數時,可以分成下面四種情形:

  • 第一種最單純,指數為正數,且小數部分小於或等於 0.5,例如求 25.3 可以寫成 25*20.3
  • 第二種是指數為正數,且小數部分大於 0.5,這時再把小數部分分成 0.5 及超過 0.5 的部分,例如計算 25.7 應寫成 25*20.5*20.2
  • 第三種是指數為負數,且小數部分超過或等於 0.5,例如計算 2-3.8,這時可以寫成 2-4*20.2
  • 第四種是指數為負數,且小數部分不超過 0.5,例如計算 2-3.2,這時可以寫成 2-4*20.5*20.3

但是實際上撰寫程式時,如果用 FRNDINT 向負無限大方向捨入,求出整數部分,再用指數減去整數部分得到小數部分,那第一種情形與第三種情形是一樣的,第二種情形與第四種情形是一樣的,所以實際上 只要考慮小數部分是否超過 0.5 兩種情形就可以了。完整的程式如下,我將它取名為 TWO_PX0X.ASM( 0 表示 8087 以上可使用,第二個 X 表示只能用於 EXE 檔):

;目的:求 2 的次方數,此指數可以是整數、負數、浮點數;輸入:ST(0):指數;輸出:ST(0):2的次方數;限制:8087以上均可使用且只能用於 EXE 檔;此副程式用到堆疊暫存器深度為 ST(3);原理是利用 2a+b=2a*2b,因為 8087 指令 FSCALE 只能計算 2 的整;數次方,F2XM1 只能計算 2 的小數次方,此小數必須在 0 和 0.5 之間        .8087;***************************************data    segment byte    public  'data'  ;10half    dd      0.5             ;11 短實數 0.5cw      dw      ?               ;12 控制字組sw      dw      ?               ;13 狀態字組data    ends;***************************************code    segment byte    public  'code'  ;16        assume  cs:code,ds:data ;17        public  two_p_x_0x      ;18 p表示求指數次方、x表指數、;-----------------------------------0表示8087以上可使用、x表示用EXE執行檔two_p_x_0x      proc    near        fstcw   cw              ;21 取得控制字組        fwait                   ;22 等待 8087 儲存完畢        push    cw              ;23 保存原控制字組        and     cw,0f3ffh       ;24 使控制字組變成向負無限大捨入,欲達此目        or      cw,00400h       ;25 的必須使控制字組第 10、11 位元變為 01        fldcw   cw              ;26 載入新的控制字組        fld     st      ;   x   ;   x           ;27        frndint         ;i=int x;   x           ;28 向負無限大捨入        pop     cw                              ;29 取回舊的控制字組        fldcw   cw      ;   i   ;   x           ;30 載入舊的控制字組        fsub    st(1),st;   i   ; f=x-i         ;31 ST(1)為小數部分f        fxch            ;   f   ;   i        fld     half    ;  0.5  ;   f   ;   i   ;33 載入 0.5        fxch            ;   f   ;  0.5  ;   i   ;34 調整小數部        fprem           ; adj f ;  0.5  ;   i   ;35 分是否超0.5        fstsw   sw                              ;36 取得狀態字組        fstp    st(1)   ;   f   ;   i           ;37 去掉 0.5        f2xm1           ; 2f-1  ;   i           ;38 求 2 的小數部分次方-1        fld1            ;   1   ; 2f-1  ;   i   ;39 載入 1        faddp   st(1),st;  2f   ;   i           ;40 完成 2 的小數部分次方

        test    sw,200h         ;41 比較小數部分是否小於 0.5        jz      less_half       ;42 小於

        fld1            ;   1   ;  2f  ;   i   ;45 大於等於時小數部        fadd    st,st   ;   2   ;  2f  ;   i   ;46 分還得乘上根號二        fsqrt           ; SQ(.5);  2f  ;   i   ;47 ST 為根號二        fmulp   st(1),st;SQ()2f;   i           ;48 完成 2 的小數部分次方less_half:        fscale          ;  2x  ;   i           ;50 已求得 2x        fstp    st(1)   ;  2x                  ;51 去掉整數部分        ret             ;52 返回主程式two_p_x_0x      endp;---------------------------------------code    ends;***************************************        end     two_p_x_0x

您可以將這個副程式加入自己的程式庫,這個副程式含有兩個區段,所以只能用於 EXE 格式的執行檔,當您由主程式呼叫這個副程式時,主程式的程式碼區段應宣告為『code segment public 'code'』,資料區段應宣告為『data segment public 'data'』,這樣 LINK.EXE 就能使主程式的資料區段與 two_p_x_0x 副程式的資料區段合而為一,主程式的程式碼區段與 two_p_x_0x 副程式的程式碼區段合而為一,請參考第十一章。

觀察

小木偶來示範如何使用這個副程式。底下這個程式將計算 2 的 3.8 次方,小木偶把它命名為 TST2P.ASM,TST 是 test 之意。

        .8087;***************************************stack   segment stack        dw      40h dup (?)stack   ends;***************************************data    segment public  'data'power   dq      3.8answer  dq      ?data    ends;***************************************code    segment public  'code'        assume  cs:code,ds:data        extrn   two_p_x_0x:near;---------------------------------------main    proc    farstart:  push    ds        sub     ax,ax        push    ax        mov     ax,data        mov     ds,ax

        finit        fld     power        call    two_p_x_0x        fstp    answer        retmain    endp;---------------------------------------code    ends;***************************************        end     start

這個程式很簡單,所以小木偶沒有加上什麼註解,只在區段宣告處要注意的地方以白色標出來而已。

H:/HomePage/SOURCE>path h:/homepage/masm50;%path% [Enter]                      → 設定路徑,以後免輸入『../masm50/』H:/HomePage/SOURCE>masm tst2p; [Enter]Microsoft (R) Macro Assembler Version 5.00Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.

  51576 + 365352 Bytes symbol space free

      0 Warning Errors      0 Severe  Errors

H:/HomePage/SOURCE>link tst2p [Enter]

Microsoft (R) Personal Computer Linker  Version 2.40Copyright (C) Microsoft Corp 1983, 1984, 1985.  All rights reserved.

Run File [TST2P.EXE]:[Enter]List File [NUL.MAP]:[Enter]Libraries [.LIB]:myasmlib [Enter]

上面是組譯以及連結的步驟,底下用 SYMDEB.EXE 載入 TST2P.EXE 來觀察看看。

H:/HomePage/SOURCE>symdeb tst2p.exe [Enter]Microsoft (R) Symbolic Debug Utility  Version 4.00Copyright (C) Microsoft Corp 1984, 1985.  All rights reserved.

Processor is [80286]-t [Enter]AX=0000  BX=0000  CX=0127  DX=0000  SP=007E  BP=0000  SI=0000  DI=0000DS=2201  ES=2201  SS=2211  CS=221B  IP=0001   NV UP EI PL NZ NA PO NC221B:0001 2BC0           SUB    AX,AX-t [Enter]AX=0000  BX=0000  CX=0127  DX=0000  SP=007E  BP=0000  SI=0000  DI=0000DS=2201  ES=2201  SS=2211  CS=221B  IP=0003   NV UP EI PL ZR NA PE NC221B:0003 50             PUSH   AX-t [Enter]AX=0000  BX=0000  CX=0127  DX=0000  SP=007C  BP=0000  SI=0000  DI=0000DS=2201  ES=2201  SS=2211  CS=221B  IP=0004   NV UP EI PL ZR NA PE NC221B:0004 B81922         MOV    AX,2219  →資料區段的區段位址-t [Enter]AX=2219  BX=0000  CX=0127  DX=0000  SP=007C  BP=0000  SI=0000  DI=0000DS=2201  ES=2201  SS=2211  CS=221B  IP=0007   NV UP EI PL ZR NA PE NC221B:0007 8ED8           MOV    DS,AX-t [Enter]AX=2219  BX=0000  CX=0127  DX=0000  SP=007C  BP=0000  SI=0000  DI=0000DS=2219  ES=2201  SS=2211  CS=221B  IP=0009   NV UP EI PL ZR NA PE NC221B:0009 9B             WAIT-dl 0 l2 [Enter]2219:0000  66 66 66 66 66 66 0E 40  +0.38E+1  → 2 的 3.8 次方2219:0008  00 00 00 00 00 00 00 00  +0.0E+0   → answer 位址-ds 10 l1 [Enter]2219:0010  00 00 00 3F  +0.5E+0 →在副程式 two_p_x_0x 所定義的變數 half-dw ds:14 L2 [Enter]2219:0014  0000 0000 →在副程式 two_p_x_0x 所定義的變數 cw 和 sw-u cs:0 [Enter]221B:0000 1E             PUSH   DS221B:0001 2BC0           SUB    AX,AX221B:0003 50             PUSH   AX221B:0004 B81922         MOV    AX,2219221B:0007 8ED8           MOV    DS,AX221B:0009 9B             WAIT221B:000A DBE3           FINIT221B:000C 9B             WAIT-db ds:0 L30 [Enter]2219:0000  66 66 66 66 66 66 0E 40-00 00 00 00 00 00 00 00  ffffff.@........2219:0010  00 00 00 3F 00 00 00 00-00 00 00 00 00 00 00 00  ...?............2219:0020  1E 2B C0 50 B8 19 22 8E-D8 9B DB E3 9B DD 06 00  .+@P8.".X.[c.]..

仔細觀察整個程式的資料區段是在 2219:0000 到 2219:0017 處,最前面的 8 個位元組(白色)是 3.8,再來的 8 個位元組(紅色)是 answer 變數,這兩個都在主程式中宣告的;接下來的 4 個位元組(藍色)是 0.5,再來的一個字組(橘色)是 cw,再來的一個位元組(紫色)是 sw,這三個變數是在 two_p_x_0x 中宣告,和在主程式中的資料結合成一個資料區段。

接下來是程式碼區段,也就是命名為『code』的區段,資料區段後還有 8 個位元組沒用到,但是 code 區段卻從 221B:0000 處開始,這是因為沒有特別指明 MASM 會把區段設定從每一節(para)處開始,所謂一節是指 10H 個位元組,請參考第 11 章。221B:0000 這個位址事實上和 2219:0020 這個位址是同一個位址,不信?您看看它們的內容都是 1E 2B C0 ……。最後再來看看 LINK.EXE 是如何把主程式的程式碼與 two_p_x_0x 副程式的程式碼連在一起。

-u cs:9 36 [Enter]221B:0009 9B             WAIT221B:000A DBE3           FINIT221B:000C 9B             WAIT221B:000D DD060000       FLD    QWord Ptr [0000]221B:0011 E80600         CALL   001A221B:0014 9B             WAIT221B:0015 DD1E0800       FSTP   QWord Ptr [0008]221B:0019 CB             RETF221B:001A 9B             WAIT221B:001B D93E1400       FSTCW  [0014]221B:001F 9B             WAIT221B:0020 FF361400       PUSH   [0014]221B:0024 81261400FFF3   AND    Word Ptr [0014],F3FF221B:002A 810E14000004   OR     Word Ptr [0014],0400221B:0030 9B             WAIT221B:0031 D92E1400       FLDCW  [0014]221B:0035 9B             WAIT221B:0036 D9C0           FLD    ST(0)

很明顯的,藍色部份就是主程式,橘色部份是 two_p_x_0x 副程式,這是因為在副程式中,假指令『segment』用了『byte』選項,所以副程式的程式碼區段可以由任意位址開始,因此 LINK.EXE 將副程式緊密的接在主程式後面。請參考第 11 章以求融會貫通。

-g [Enter]

Program terminated normally (0)-DL 221B:0 L2 [Enter]221B:0000  66 66 66 66 66 66 0E 40  +0.38E+1221B:0008  1F E1 DB DA 8C DB 2B 40  +0.1392880901273798E+2 →23.8

執行看看,果然已經計算出 23.8 了。


用 Pentium 計算 2x 的副程式

假如使用 Pentium 來計算 2x 的話,情形就沒有這麼複雜,因為 Pentium 等級以上的 CPU (或 NPU) 其 F2XM1 的指數可以是在 0 到 1 之間,故不用再考慮指數的小數部分是否超過 0.5,完整的副程式如下,小木偶將它的原始檔案取名為 TWO_PX5X.ASM:

       .8087;***************************************data    segment byte    public  'data'cw      dw      ?               ;04 控制字組data    ends;***************************************code    segment byte    public  'code'        assume  cs:code,ds:data        public  two_p_x_5x;---------------------------------------;目的:求 2 的次方數,此指數可以是整數、負數、浮點數;輸入:ST(0):指數;輸出:ST(0):2的次方數;限制:Pentium 以上均可使用且只能用於 EXE 檔;此副程式用到堆疊暫存器深度為 ST(3);備註:1.此副程式可以用在 pentium 及其以上等級的 FPU。;      2.此副程式原理是利用 2a+b=2a*2b,a 表示整數部分,b表示小數部分two_p_x_5x      proc    near        fstcw   cw       ;19 取得控制字組        fwait            ;20 等待 pentium 儲存完畢        push    cw       ;21 保存原控制字組        and     cw,0f3ffh;22 使控制字組變成向負無限大捨入,欲達此目        or      cw,00400h;23 的必須使控制字組第 10、11 位元變為 01        fldcw   cw       ;24 載入新的控制字組        fld     st       ;   x   ;   x   ;25        frndint          ;i=int x;   x   ;26 向負無限大捨入        pop     cw                       ;27 取回舊的控制字組        fldcw   cw       ;   i   ;   x   ;28 載入舊的控制字組        fsub    st(1),st ;   i   ; f=x-i ;29 ST(1)為小數部分,f        fxch             ;   f   ;   i   ;30 交換        f2xm1            ; 2f-1 ;   i           ;31 求 2 的小數部分次方        fld1             ;   1   ; 2f-1 ;   i   ;32 載入 1        faddp   st(1),st ;  2f  ;   i           ;33 完成 2 的小數部分次方        fscale           ;  2x  ;   i           ;34 使 2        fstp    st(1)    ;  2x                  ;35 去掉整數部分        ret              ;36 返回主程式two_p_x_5x      endp;---------------------------------------code    ends;***************************************        end     two_p_x_5x

這個副程式也可以加入我們的程式庫中,現在的電腦等級都在 Pentium !!! 以上,因此這個副程式應該要比 two_p_x_0x 還常用才對。


求 XY

雖然 FPU 並沒有提供直接計算 XY 的指令,但是有了求 2X 的副程式,很容易就能利用數學公式寫出求 XY 的副程式。

XY = 2log2XY = 2Ylog2X

根據上面的公式,我們只要用指令 FYL2X 求出 Ylog2X 即可(但有限制),再把這個數值存在 FPU 的堆疊頂,呼叫 two_p_x_5x 即可求出 XY,程式如下:

        .8087;***************************************data    segment byte    public  'data'sw      dw      ?       ;04 狀態字組data    ends;***************************************code    segment byte    public  'code'        assume  cs:code,ds:data;---------------------------------------;計算 XY 之值,原理:XY=2log2XY=2Ylog2X;輸入:ST - 底數,X;      ST1- 指數,Y;輸出:若錯誤(零的零次方或底數為負值),則進位旗標被設定;;      若沒錯誤,則進位旗標被清除,且 ST 為 X 的 Y 次方,XY,堆疊深度減一;限制:這個副程式只能用在 Pentium 以上,組譯成 EXE 檔                    public  x_p_y_5x        extrn   two_p_x_5x:nearx_p_y_5x        proc    near        ftst        push    ax        fstsw   sw        fwait        mov     ax,sw           ;23 把狀態字組移入 AX        sahf                    ;24 把狀態字組的高位元部份移入旗標        jz      zero            ;25 底數為0        jc      err1            ;--st0--;--st1--;26 底數為負數                                ;   X   ;   Y        fyl2x                   ; Ylog2X;        call    two_p_x_5x      ;   XY  ;exit:   clc             ;30 清除進位旗標        pop     ax        ret

zero:   fcomp           ;34 底數為零,彈出底數        ftst            ;35 檢查指數是否為零        fstsw   sw        mov     ax,sw        sahf        jz      err2        fcomp           ;40 底數為零,指數不為零        fldz            ;41 彈出底數(第34行)再彈出        jmp     exit    ;42 指數(上一行),再載入零

err1:   fcomp           ;44err2:   fcomp           ;45 指數與底數均為零或底數為負數        stc             ;46 設定進位旗標        pop     ax        retx_p_y_5x        endp;---------------------------------------code    ends;***************************************        end     x_p_y_5x

這個副程式沒什麼新的觀念了,只是要注意的是在數學上,零的零次方是無意義的,因此小木偶加上了一段程式檢查指數與底數是否同時為零,如果是這樣的話那就設定進位旗標傳回主程式,使主程式知道這是輸入錯誤的引數。


供 COM 程式庫使用的副程式

變數位址錯誤的問題

前面所建立的兩個副程式:two_p_x_5x 以及 y_p_x_5x 因為有兩個區段,因此只能製作成 EXE 檔,如果要製作成 COM 檔所能使用的副程式會必須克服另一個問題,那就是副程式的資料與程式碼是混在同一區段裏,當存取副程式的變數時,CPU 所得到的變數位址是錯誤的。

以 two_p_x_5x 為例,如果您以為只要把 cw 移到副程式的程式碼內,並刪去 data 區段,變成下面這樣,存成 TWO_PX5C.ASM:

       .8087;***************************************code    segment byte        assume  cs:code,ds:code        public  two_p_x_5c;---------------------------------------two_p_x_5c      proc    near        fstcw   cw        fwait        push    cw        and     cw,0f3ffh        or      cw,00400h        fldcw   cw        fld     st       ;   x   ;   x   ;        frndint          ;i=int x;   x   ;        pop     cw                       ;        fldcw   cw       ;   i   ;   x   ;        fsub    st(1),st ;   i   ; f=x-i ;        fxch             ;   f   ;   i   ;        f2xm1            ; 2f-1 ;   i           ;        fld1             ;   1   ; 2f-1 ;   i   ;        faddp   st(1),st ;  2f  ;   i           ;        fscale           ;  2x  ;   i           ;        fstp    st(1)    ;  2x                  ;        ret              ;返回主程式cw      dw      ?        ;cw 變數two_p_x_5c      endp;---------------------------------------code    ends;***************************************        end     two_p_x_5c

如果上述副程式加入程式庫,再以下面的主程式連結:

        .8087;***************************************code    segment public  'code'        assume  cs:code,ds:code        extrn   two_p_x_5c:near        org     100h;---------------------------------------main    proc    farstart:  jmp     short beginp1      dq      0.5     ;0.5 次方ans1    dq      ?       ;求 2 的 0.5 次方答案處p2      dq      -2.2    ;-2.2 次方ans2    dq      ?       ;求 2 的 -2.2 次方begin:  finit        fld     p1        call    two_p_x_5c        fstp    n1        fld     p2        call    two_p_x_5c        fstp    n2        retmain    endp;---------------------------------------code    ends;***************************************        end     start

結果,cw 變數會在 40H 處,不信的話,請看 SYMDEB.EXE 載入的情形:

Microsoft (R) Symbolic Debug Utility  Version 4.00Copyright (C) Microsoft Corp 1984, 1985.  All rights reserved.

Processor is [80286]-u [Enter]220E:0100 EB20           JMP    0122 →跳過資料區220E:0102 0000           ADD    [BX+SI],AL220E:0104 0000           ADD    [BX+SI],AL220E:0106 0000           ADD    [BX+SI],AL220E:0108 E03F           LOOPNZ 0149220E:010A 0000           ADD    [BX+SI],AL220E:010C 0000           ADD    [BX+SI],AL220E:010E 0000           ADD    [BX+SI],AL-u 122 [Enter]220E:0122 9B             WAIT220E:0123 DBE3           FINIT220E:0125 9B             WAIT220E:0126 DD060201       FLD    QWord Ptr [0102]220E:012A E81300         CALL   0140 →呼叫 two_p_x_5c 副程式220E:012D 9B             WAIT220E:012E DD1E0A01       FSTP   QWord Ptr [010A]220E:0132 9B             WAIT-u 140 [Enter]220E:0140 9B             WAIT220E:0141 D93E4000       FSTCW  [0040] →把控制字組存入 CW 變數220E:0145 9B             WAIT            但 CW 位址是錯的220E:0146 FF364000       PUSH   [0040]220E:014A 81264000FFF3   AND    Word Ptr [0040],F3FF220E:0150 810E40000004   OR     Word Ptr [0040],0400220E:0156 9B             WAIT220E:0157 D92E4000       FLDCW  [0040]-u 179 [Enter]220E:0179 9B             WAIT220E:017A D9FD           FSCALE220E:017C 9B             WAIT220E:017D DDD9           FSTP   ST(1)220E:017F C3             RET220E:0180 0000           ADD    [BX+SI],AL →正確的 CW 變數所在處

您會發現,CW 變數變成在 40H 的位址了,這當然是不對的,COM 檔的程式是由 100H 處開始,100H 之前是 PSP。之所以會這樣是因為 MASM 把 dw 等假指令都看成直接接在程式碼的 CPU 指令,MASM 把這個記憶體位址保留給 CW 使用。當用 LINK 連結時,也是把這個記憶體位址接在程式碼後面,並不是接在資料後面,所以存取 CW 變數時,CW 表示『該變數距離副程式起始位址多少位元組』,但不是連結後的正確位址。您可以在副程式組譯時,製作列表檔觀察證明這一點:

H:/HomePage/SOURCE>masm two_px5c [Enter]Microsoft (R) Macro Assembler Version 5.00Copyright (C) Microsoft Corp 1981-1985, 1987.  All rights reserved.

Object filename [two_px5c.OBJ]:[Enter]Source listing  [NUL.LST]: two_px5c[Enter] →製作列表檔Cross-reference [NUL.CRF]: [Enter]

  50904 + 366040 Bytes symbol space free

      0 Warning Errors      0 Severe  Errors

觀察 TWO_PX5C.LST 您可以看到以下片段:

                N a m e            Type     Value   Attr

CW . . . . . . . . . . . . . . .   L WORD  0040    CODE

TWO_P_X_5C . . . . . . . . . . .    N PROC  0000    CODE    Global  Length = 0042

@FILENAME  . . . . . . . . . . .     TEXT  two_px5c      

CW 變數在 0040H 的地方,且在副程式最後面,長度為字組,長一個字組,所以整個 TWO_P_X_5C 長 42H 個位元組。

程式碼內嵌變數時,取得正確位址之方法

知道錯誤的原因了,接下來要如何解決呢?我們現在已經知道當程式庫內的副程式內嵌資料或變數時,這些資料或變數所表示的位址並不是連結後真正位址,而是距離該副程式起始位址多少個位元組,所以變數真正位址應該是『該變數距離副程式起始位址多少個位元組』加上『副程式起始位址』,而『該變數距離副程式起始位址多少個位元組』其實就是上述的錯誤位址,也就是該變數所代表的位址。

至於『副程式起始位址』應為『副程式某一個正在執行之指令位址』減去『該指令距離副程式起始位址多少位元組』。其實『副程式某一個正在執行之指令位 址』就存在 CS:IP 裏,所以只要取得 IP 之值即可,可惜翻遍所有 80X86 指令都沒有這個指令,不過我們有變通的方法。當程式呼叫副程式時,會把要執行的位址推入堆疊,因此我們只要『假裝』呼叫副程式,再到堆疊取出堆疊頂就得到 正執行的位址了。而『該指令距離副程式起始位址多少位元組』可以用一個運算子,THIS,來取得(稍後介紹 THIS)。

看完上面說明,不被搞得頭昏眼花才怪,請參考下圖、上文、底下的 two_p_x_5c 副程式原始碼以及用 SYMDEB 載入的情形,用力想想看吧。或者假如您還有更好的方法說明這段複雜的位址關係,請來信指導,小木偶在此謝謝。

THIS 運算子

這個運算子是用來取得 THIS 所在位址距程式起始位址多少個位元組。它必須在後面接上形態 (type),形態可以是 BYTE、WORD、DWORD、QWORD、TBYTE,如果是用在標記,其形態可以是 NEAR、FAR、PROC。

由堆疊中取得的『副程式某一個正在執行之指令位址』減去由 THIS 運算子取得的『該指令距離副程式起始位址多少位元組』就是『副程式起始位址』,把它存入 BX 暫存器。

基底相對定址法

BX 之值再加上『該變數距離副程式起始位址多少個位元組』就是變數正確位址,這時必須用『基底相對定址法』來取得變數正確位址:

[BX+相對值][BP+相對值]

相對值可以是常數或變數,如果是變數的話,也可以寫成

變數[BX]變數[BP]

以這個程式為例,cw[bx] 的意思是會到 BX 暫存器與 cw 相加後所得的位址去取得該位址的數值,如果沒有特別用『凌越區段』則會取得 DS:BX+cw 位址之值。如果暫存器是 BP 的話,則會取得 SS:BP+相對值所指的位址之數值。

凌越區段

取得變數位址後,還有一個問題有待克服,那就是我們所取得的位址其實只是偏移位址,而變數完整的位址是包含區段位址,可是這個變數包含在副程式內, 副程式會被 LINK.EXE 安排在程式碼的區段,並非在資料區段,因此在存取該變數時,必須指定在程式碼區段,請參考原始程式第 17 行以及第 19 到第 22 行的寫法,也起參考第 9 章暫存器間接定址的那一段落。

整理

綜合上述,底下的 two_p_x_5c 副程式的第 17 行

fstcw   cs:cw[bx]

其實相當於

add     bx,cwfstcw   cs:[bx]

two_p_x_5c 原始程式

小木偶把 two_p_x_5x 副程式稍稍改寫變成 two_p_x_5c,如下:

;***************************************code    segment byte        assume  cs:code,ds:code        public  two_p_x_5c;---------------------------------------;目的:計算 2 的任意次方;輸入:ST--指數;輸出:ST--2 的冪方數;備註:1.此副程式可以用在 pentium 及其以上等級的 FPU 供給 COM、EXE 檔呼叫。;      2.此副程式原理是利用 2a+b=2a*2b,a 表示整數部分,b表示小數部分two_p_x_5c      proc    near        push    bx              ;12 程式開始處        call    rel_ad          ;13 『假裝』呼叫 rel_adaddr    equ     this word       ;14 addr記錄『該指令距離副程式起始位址多少位元組』rel_ad: pop     bx              ;15 『副程式某一個正在執行之指令位址』存於BX        sub     bx,offset addr  ;16 BX 為『副程式起始位址』        fstcw   cs:cw[bx]       ;17 取得控制字組        fwait                   ;18 等待 pentium 儲存完畢        push    cs:cw[bx]       ;19 保存原控制字組        and     cs:cw[bx],0f3ffh;20 使控制字組變成向負無限大捨入,欲達此目        or      cs:cw[bx],00400h;21 的必須使控制字組第 10、11 位元變為 01        fldcw   cs:cw[bx]       ;22 載入新的控制字組        fld     st              ;   x   ;   x           ;23        frndint                 ;i=int x;   x           ;24 向負無限大捨入        pop     cs:cw[bx]                               ;25 取回舊的控制字組        fldcw   cs:cw[bx]       ;   i   ;   x           ;26 載入舊的控制字組        fsub    st(1),st        ;   i   ; f=x-i         ;27 ST(1)為小數部分,f        fxch                    ;   f   ;   i           ;28 交換        f2xm1                   ; 2f-1 ;   i           ;29 求 2 的小數部分次方        fld1                    ;   1   ; 2f-1 ;   i   ;30 載入 1        faddp   st(1),st        ;  2f  ;   i           ;31 完成 2 的小數部分次方        fscale                  ;  2x  ;   i           ;32 使 2        fstp    st(1)           ;  2x                  ;33 去掉整數部分        pop     bx              ;34 存回 BX        ret                     ;35 返回主程式cw      dw      ?               ;36 控制字組two_p_x_5c      endp;---------------------------------------code    ends;***************************************        end     two_p_x_5c

小木偶把新修改後的 two_p_x_5c 加入程式庫再用上述主程式重新連結,用 SYMDEB 載入其 COM 檔觀察:

-u 140 [Enter] →直接到 two_p_x_5c 副程式起始處2118:0140 53             PUSH   BX2118:0141 E80000         CALL   0144 2118:0144 5B             POP    BX2118:0145 81EB0400       SUB    BX,00042118:0149 9B             WAIT2118:014A 2ED9BF5100     FSTCW  CS:[BX+0051]2118:014F 9B             WAIT2118:0150 2EFFB75100     PUSH   CS:[BX+0051]-g 140 [Enter]AX=0000  BX=0000  CX=0093  DX=0000  SP=FFFC  BP=0000  SI=0000  DI=0000DS=2118  ES=2118  SS=2118  CS=2118  IP=0140   NV UP EI PL NZ NA PO NC2118:0140 53             PUSH   BX →副程式起始於 140H-t [Enter]AX=0000  BX=0000  CX=0093  DX=0000  SP=FFFA  BP=0000  SI=0000  DI=0000DS=2118  ES=2118  SS=2118  CS=2118  IP=0141   NV UP EI PL NZ NA PO NC2118:0141 E80000         CALL   0144 → 假裝呼叫副程式 rel_ad-t [Enter]AX=0000  BX=0000  CX=0093  DX=0000  SP=FFF8  BP=0000  SI=0000  DI=0000DS=2118  ES=2118  SS=2118  CS=2118  IP=0144   NV UP EI PL NZ NA PO NC2118:0144 5B             POP    BX →由堆疊取得呼叫後返回位址-t [Enter]AX=0000  BX=0144  CX=0093  DX=0000  SP=FFFA  BP=0000  SI=0000  DI=0000DS=2118  ES=2118  SS=2118  CS=2118  IP=0145   NV UP EI PL NZ NA PO NC2118:0145 81EB0400       SUB    BX,0004 → addr 距副程式起始處 4 個位元組-t [Enter]AX=0000  BX=0140  CX=0093  DX=0000  SP=FFFA  BP=0000  SI=0000  DI=0000DS=2118  ES=2118  SS=2118  CS=2118  IP=0149   NV UP EI PL NZ NA PO NC2118:0149 9B             WAIT-t [Enter]AX=0000  BX=0140  CX=0093  DX=0000  SP=FFFA  BP=0000  SI=0000  DI=0000DS=2118  ES=2118  SS=2118  CS=2118  IP=014A   NV UP EI PL NZ NA PO NC2118:014A 2ED9BF5100     FSTCW  CS:[BX+0051]                 CS:0191=0000-u 187 [Enter]                         ﹂→基底相對定址法2118:0187 DEC1           FADDP  ST(1),ST2118:0189 9B             WAIT2118:018A D9FD           FSCALE2118:018C 9B             WAIT2118:018D DDD9           FSTP   ST(1)2118:018F 5B             POP    BX2118:0190 C3             RET2118:0191 0000           ADD    [BX+SI],AL → CW 真正位址

上述程式的第 15 行,POP BX 就是取得『某一個正在執行之指令位址』,而第 14 行的 addr equ this word 所代表的數值,就是『POP BX 這個指令距離副程式起始位址多少位元組』。rel_ad:和 POP BX 在記憶體裏所佔的位址,在 MASM 組譯時並沒有完全確定,MASM 只是將它距離副程式的起始位址多少個位元組寫入 OBJ 檔,待連結時才看主程式的大小真正計算出來。addr 所佔的位址在 MASM 組譯時已經確定,連結時也不會更改。所以假如 two_p_x_5c 副程式不與其他程式連結(不被其他程式呼叫),單獨載入記憶體內,addr、rel_ad:、POP BX 這三個所佔的位址都是相同的,但是如果它與其它程式連結時,rel_ad:、POP BX 所佔的位址會隨主程式大小變動,但是 addr 仍然不變。

當程式第 13 行『假裝』呼叫副程式時,會把下一個要執行的指令 POP BX 位址推入堆疊,第 14 行是假指令,只是寫給組譯器看的,所以第 13 行執行完畢直接到第 15 行,POP BX,取得堆疊頂端的數值,也就是 POP BX『某一個正在執行之指令位址』,下一行減去『POP BX 這個指令距離副程式起始位址多少位元組』就得到『副程式起始位址』,此後只要存取變數,只需用『變數名[BX]』就能得到正確位址。


註一:80287、80387、 80487 的 F2XM1 的引數部分是在 0 到 0.5 還是 0 到 1.0 之間,小木偶沒有更詳細的資料,所以無法確定。而 Pentium 等級的電腦,小木偶還有一台 Pentium-100 的筆記型電腦,所以可以測試確定在 0 到 1.0 之間。

註二:此限制主要是在 log 的引數必須為正值。


回到首頁, 到第二十三章, 到第二十五章

FPU (3) 指數相关推荐

  1. 上海天氣情況及空氣質量指數

    天氣數據覆蓋範圍 之前一直關注的是 Twitter 上面 美帝上海領館的 空氣檢測數據 覺得不是很方便,畢竟只是一條 Tweet ,於是用了 aqicn 的 widget 做了一個 頁面 方便查看. ...

  2. 信用指數旁的星星表示什麼?

    信用指數達 10 分的 eBay 會員就會獲得信用評價星星.了解更多如何爭取更高信用指數. 在大部分情況下,會員會收到: +1 分(每次得到正面的評語和指數時) 0 分(每次得到中性的評語和指數時) ...

  3. 孫丕恕:計算力已成為數字經濟先行指數 決定未來發展潛力

    中國網財經9月20日訊(記者暢帥帥)2018夏季達沃斯論壇9月18日至20日在天津召開."創新型社會"."智慧城市"等成為本屆夏季達沃斯論壇熱議的話題,浪潮集團 ...

  4. FPU (1) 简介

    FPU 简介 FPU 是什麼 FPU 稱為浮點運算器是 floating-point processor unit 的縮寫它是一個處理數學運算的晶片.早在 1979 年英特爾就為了搭配 8086/80 ...

  5. matlab shortest函数,MATLAB函數graphallshortestpaths不返回對稱矩陣

    我正在使用MATLAB函數graphallshortestpaths來計算無向網絡頂點之間的最短路徑.無向網絡作爲加權邊緣列表文件給出,您可以在其中找到here.MATLAB函數graphallsho ...

  6. Big Data應用:以玩家意見之數據分析來探討何謂健康型線上遊戲(上)

    首先,所有資料都可以從網路上找到,只是我做了一些分析與整理而已.純粹分享心得~~ 最近再做研究的時候我跟我的同事K先生在某次偶然的討論中發現了一件有趣的事情. [疑~~~~~~~新楓之谷的玩家人氣指數 ...

  7. MATLAB保存数据为dat格式,將matlab中數據保存為txt或dat格式

    將matlab中數據保存為txt或dat格式[轉] 具體的命令是:用save *.txt -ascii x x為變量*.txt為文件名,該文件存儲於當前工作目錄下,再打開就可以打開后,數據有可能是以指 ...

  8. ASP与數据庫,文本文件鏈接精髓

    ASP与數据庫,文本文件鏈接精髓<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office&quo ...

  9. 【機器學習2021】預測本頻道觀看人數 (下) - 深度學習基本概念簡介

    机器学习 2021 - Introduction of Machine / DeepLearning \qquadLinear 的 Model 也許太過簡單了,怎麼說它太過簡單呢?我們可以想像說 x1 ...

最新文章

  1. 4G EPS 中的 FDD/TDD 无线帧
  2. 网络工程师学习资料:路由器配置案例分析
  3. 每日程序C语言47-找到年龄最大的人并输出
  4. 深度学习导论(6)误差计算
  5. java部署平台_开源Java自动化部署平台JDeploy
  6. Ubuntu16.04下codeblocks16.01安装,适用于不同的架构
  7. html 中加载字体太慢,前端解决中文字体加载慢的问题
  8. OpenShift 4 - DevSecOps (2) - 修复 RHACS 发现的安全隐患
  9. Mysql更新计数器_MySQL实现计数器如何在高并发场景下更新并保持数据正确性
  10. endnote大客户版_Endnote软件的使用,有图有干货!
  11. oracle 归档日志激增,一次归档日志激增的分析.
  12. raid卡 4k 设置 linux,硬盘“大户”看过来,手把手教你组建 RAID 磁盘阵列
  13. JAVA超市综合管理信息区块链系统毕设论文
  14. Win10删除微软拼音输入法
  15. Plugin: Memory Dump by aeon update 12.10
  16. html5摄像头 在线演示,基于HTML5实现的超酷摄像头(HTML5 webcam)摄
  17. 350个运动摄影lr调色预设(含lr预设导入教程)
  18. 吉他调音软件AP Guitar Tuner使用经验
  19. linux+gunzip解压命令,Linux中的Gunzip命令详解
  20. 生活随记-四十年流水线

热门文章

  1. 01 导论【计量经济学及stata应用】
  2. Curl windows下载地址
  3. 基于混沌映射的自适应樽海鞘群算法-附代码
  4. 如何创建网站:2021年看这份指南就够了
  5. .NET Framework 介绍
  6. 浪潮孙丕恕:“云+数”赋能 共享数字经济
  7. Java并发技术要点梳理
  8. 最详细的教程,教你如何彻底关闭Chrome浏览器自动更新
  9. 字节跳动接力反内卷:宣布取消大小周,直接降薪 15%?
  10. 解决AttributeError: ‘NoneType‘ object has no attribute ‘val‘ if left.val!=right.val:Line 17 问题