어셈블리

어셈블리 강좌4

RSMaster 2019. 7. 9. 20:02

5.10 곱셈 명령과 구조
--------------------------------------------------------------------------------
곱셈 명령 MUL(multiply)에서는
 8비트 * 8비트, 혹은 16비트 * 16비트 를 계산할 수가 있습니다.
사용할 때의 제한 :
우선 곱셈할 한쪽은 반드시 AL 레지스터(혹은 AX 레지스터 )에 넣어두지 않으면 안될
니다. 결과는 AX 레지스터(혹은 DX 레지스터를 상위 16비트, AX 레지스터를 하위 16비트
로 간주한 32비트 레지스터 )에 저장됩니다.
따라서 곱셈 명령의 오퍼랜드는 하나이다.

8비트 곱셈                                     곱셈 결과 저장
+---+     +------------------------------+    +---+
|AL |  *  | 8비트의 레지스터 혹은 메모리 |--->|AX |
+---+     +------------------------------+    +---+

16비트 곱셈                                    곱셈결과 저장
+---+     +------------------------------+    +---+---+
|AX |  *  |16비트의 레지스터 혹은 메모리 |--->|DX |AX |
+---+     +------------------------------+    +---+---+
예) MUL   BL
여기서 지정된 레지스터가 8비트인가 , 16비트인가에 따라서
자동적으로 AX 레지스터가 결정됩니다.
부호 없는 곱셈 MUL(multiply)
부호 있는 곱셈 IMUL(integer multiply)

사용할 때의 주의할 점: 오퍼랜드가 메모리일 때
예)
        MUL     DATA1
        .............
DATA1   DB      20H
와같은 경우에는 DATA1의 형이 바이트 변수라고 알 수 있으므로 괜찮지만
        MOV     BX, OFFSET DATA2
        MUL     [BX]
        .............
DATA2   DW      1234H
와 같은 경우, [BX]에 의해 표시되는 메모리가 바이트 단위인지, 워드 단위인지가 불명확
하므로 에러가 됩니다. 이와 같은 경우에는 [BX]에 의해 지정되는 메모리가 바이트 단
위인지를 명시하기 위해서
MUL     WORD    PTR [BX]
와 같이 사용하지 않으면 안 됩니다.

또 하나 주의할 점:  곱셈 명령에서는 직접 숫자 하고는 곱셈을 할 수 없다
예)
MUL     3
이와 같이 는 할 수 없으므로
MOV     BL,3
MUL     BL
와 같이 하도록 되어 있습니다.
또한 상수 이름은 수치로 변환되기 때문에 상수 이름도 사용할 수 없습니다.
ABC     EQU     12H
        .........
        MUL     ABC
와 같이는 쓸 수가 없습니다.

예) MUL1.ASM
부호 없는 두 개의 메모리끼리, 부호 있는 두 메모리끼리 곱셈 보기
CODE    SEGMENT
        ASSUME  CS:CODE, DS:CODE
;
        MOV     AX, CS  ;  CS와 DS를 같게 설정
        MOV     DS, AX
;
        MOV     AL, DATA_1A
        MUL     DATA_1B
        MOV     ANS1, AX
        MOV     AX, DATA_2A
        MUL     DATA_2B
        MOV     ANS2_A, DX ;결과의 상위, 하위를 직접 변수에 넣을 때에는 2개의 변수
        MOV     ANS2_B, AX ;로 나누어야만 한다.
;
        MOV     AL, DATA_3A
        MOV     BL, DATA_3B
        IMUL    BL
        MOV     ANS3, AX
        MOV     AX, DATA_4A
        MOV     BX, OFFSET DATA_4B
        IMUL    WORD PTR [BX]
        MOV     DI, OFFSET ANS4 -+이와 같이 지정하면 변수 이름은 하나이면 된다.
        MOV     [DI], DX         |
        MOV     [DI+2], AX      -+
;
        MOV     AH,4CH
        INT     21H
;
DATA_1A DB      0 F0 H
DATA_1B DB      11H
DATA_2A DW      1234H
DATA_2B DW      2001H
DATA_3A DB      -10H
DATA_3B EQU     11H    ;EQU에 의한 정의는 어디에 있어도 상관없지만 주의가 필요
DATA_4A DW      -1000H
DATA_4B DW      1234H
;
ANS1    DW?
ANS2_A  DW?      ;16비트 숫자 곱셈은 DW로 정의 워드에 한 번에 저장할 수
ANS2_B  DW?      ;없으므로 ANS2-A 와 ANS2-B 로 나누어 저장
ANS3    DW      ?
ANS4    DD?       ;더불 워드(4바이트)를 선언
;
CODE    ENDS
        END

DATA_1B는 DB에서 정의된 변수 이름이고 , 바이트형의 속성을 가지고 있으므로
BYTE PTR의 지정은 생략할 수 있습니다.(붙여도 상관없습니다.)
DATA_2B 가 워드형의 속성을 갖고 있기 때문에 직접 곱셈을 할 수가 있습니다.
결과가 DX:AX 레지스터 쌍에 의한 32바이트 레지스터에 저장되어집니다.
이 결과를 ANS2로 전송하고 싶은데 한 번에 할 수 없으므로 DX레지스터와 AX레지스터로
나누어서 전송합니다.
레지스터의 변수 이름의 형이 일치하지 않으면 안 되므로 , ANS2를 2개의 워드형의 변수
ANS2_A와 ANS2_B로 나누고 있습니다.
변수 이름은 속성으로서 번지 이외에도 바이트인지 워드인지를 구분하는 타입을 가지고
있는 것에 주의해 주십시오
만일 ,
        MOV     ANS2, DX
        MVO     ANS2+2, AX
        .......
ANS2    DD?
ANS2는 더블 워드형(double word)의 속성을 갖고 있으므로 , 더블 워드형의  변수에 워
드형의 레지스터 값을 전송하려고 한 것으로 되어서 ERROR 가 됩니다.
이와 같은 사용법을 하고 싶다면 강제적으로 타임을 일치시키기 위해서
        MOV     WORD PTR ANS2, DX
        MOV     WORD PTR ANS2+2, AX

ANS     DD?
라고 하면 되는 것입니다.

전방 참조(forward reference)
매크로 어셈블러는 소스 프로그램을 기계어로 번역할 때 전체를 선두로부터 2번  반복
해서 읽고  지나가면서  작업을 해  나갑니다.(그  때문에 2 패스  어셈블러(two  pass 
assembler)라고 불린다.)
어셈블러가 첫 번째 선두로부터 읽어 내려갈 때에 , DATA_3B는 프로그램  끝에 있기 때
문에 아직 정의되어 있지 않으므로 , 이 시점에서는 아직 기계어를 결정할 수가  없으니
다. 그래서 어셈블러는 이 부분의 기계어를 결정하지 않은 채로 생각할 수 있는 최대  바
이트인 4바이트를 확보해두고 다음으로 진행해 나갑니다. 그리고 마지막 쪽에서 DATA_3B
가 상수이고 그 값이 11H라는 것을 알 수 있습니다. 그래서 2번째 번역 작업할 때에  미
정이었던 기계어를 결정할 수가  있읍니다.그러나 완성된 기게어는 2바이트로  할수가 
있어서, 첫 번째 확보해 두었던 4바이트 중  2바이트가 남아 버립니다. 그래서 어셈블러 
는 불필요한 2바이트 에 NOP(=no operation:아무것도 하지 않는 것  에 상당하는 90H 
를 삽입합니다.
이와 같이 첫 번째 읽었을 때에 아직 정의되어 있지 않은 라벨(상수 이름이나  변수 이름
기타 등등 )이 나타나는 것을 전방 참조(forward refenence) 라고 부릅니다.

전방참조 가 있으면 프로그램의 효율이 나쁘게 되는 것뿐만 아니라 최악의 경우에는 어
셈블 에러를 일으 킵니다. 그래서 EQU  문에 의한 정의는 가능한 한 프로그램의  선두에 
놓도록 합니다.
물론 , DB , DW에 의한 데이터의 정의도 항상 전반 참조의 문제를 가지고 있으므로  원
내는 프로그램 선두에 놓는 쪽이 좋다 라고 말할 수 있지 마 EQU 문과는 달라서  단순히 
선두에 가져다 놓으면 된다라고도 할 수 없으므로 , 이것에  대해서는 순서에 따라 다시 
설명하겠습니다.
MOV     DI, OFFSET ANS4
MOV     [DI], DX
MOV     [DI+2], AX
라고 함으로써 더블 워드는 변수로 2개의 워드형 데이터를 저장하고 있습니다.

같은 수치의 곱셈이라도 부호가 달린 것으로 부느냐 안 보느냐에 따라 결과가 다르다.
           MUL    +----->0FF0...  부호 없는 곱셈인 경우
F0 * 11  ---------+
           IMUL   +-----> FEF0...   부호 있는 곱셈인 경우


스트럭처(structure)에 의한 변수의 정의 :구조체라는 의미
데이터를 프로그램 중에 나열하는 경우
이름\항목 |   AGE  |  HEIGHT  |   WEIGHT  |
A         |  45    |   170    |   72      |
B         |  38    |   164    |   58      |
C         |  25    |   175    |   68      |
A_AGE           DB      45
A_HEIGHT        DB      170
A_WEIGHT        DB      72
B_AGE           DB      38
B_HWIGHT        DB      164
B_WEIGHT        DB      58
C_AGE           DB      25
C_HWIGHT        DB      175
C_WEIGHT        DB      68

이와 같은 방법에서의 정의는 , 데이터의 수가 증가됨에 따라 대단히 귀찮게 되고  또한 
알기 어렵게 됩니다.

MASM에서는 몇 가지 항목으로 나누어지는 것과 같은 데이터 구조를 정의할 수 있도록
스트럭쳐형 변수라고 하는 구조를 정의할 수 있도록 되어있습니다.
스트럭처형 변수를 사용하기 위해서는 미리 STRUC 의사 명령을 사용하여 데이터의 구
조를 정의해 둡니다.
AHW     STRUC      --+
AGE     DB?    |스트럭처형 변수선언을 위한 의사명령 예
HEIGHT  DB      ?    |
WEIGHT  DB      ?    |
AHW     ENDS       --+
여기에서 AHW가 스트럭처 이름이 되고 AGE, HEIGHT, WEIGHT가 필드 이름입니다. 여기에서
정의된 스트럭처 이름은 데이터의 구조에 대해 붙여진 이름으로서 변수 이름  그 자체는
아닙니다.
A       AHW     <45,170,72>
와 같은  식으로 합니다. 그리하여 A는  스트럭처 변수 이름 < > 속이 그 변수의 각 항목
에 대해 주어진 데이터의 초기치입니다. 여러 사람의 데이터를 정의하려면 다음과  같
이 합니다.
A       AHW     <45,170,72>
B       AHW     <38,164,58>
C       AHW     <25,175,68>
이것으로써 A, B, C... 이 3 개의 필드 (AGE, HEIGHT, WEIGHT)를 가지는  스트럭처형 변
수로서  정의되어 각 필도에 대응하는 데이터가 설정됩니다.
스트럭처 이름(이경우는 AHW)은 단순히 데이터의 구조를 나타내는 것이라는 점에 주의
해 주십시오.
스트럭처형 변수 내의 각각의 요소를 꺼내려면 , 스트럭처형 변수 이름 다음에 피리어드
(.)와 필드 이름을 붙인 것을 사용합니다.
MOV     AL, A.AGE
MOV     BL, A.HEIGHT
MOV     CL, A.WEIGHT
 라고 함으로써 AL  레지스터에 A 씨의 연령(=45), BL 레지스터에 A 씨의 신장(=170),
CL레지스터에  A  씨의 체중(=72)이 전송됩니다.
또한 전원의 연령의 합계를 구하려면
MOV     AL, A.AGE
ADD     AL, B.AGE
ADD     AL, C.AGE
라고 하면 되는 것입니다.(오버 플로우 처리(over flow)는 생략)


스트럭처의 사용순서는
+----------------------------------------+
|스트럭처 이름에 대한 데이터 구조의 정의 +-+
+----------------------------------------+ |
                                           |
                                           \/
+--------------------------------------------------------------+
| 변수 이름에 대하여 스트럭처 이름을 사용하여 스트럭처형        |
| 변수라는 것을 정의함과 동시에 초기화 데이터를  준다.         |
+--------------------------------------------------------------+
단순히 스트럭처 선언을 한 것만으로는 메모리 상에 데이터는 설정되지 않습니다.
각 필드  이름이 가지는 스터럭처 선두로부터의 오프셋 번지()가 표시되어있을 뿐
실제 번지상에 데이터가 세트 되는 것은 아닙니다.
 A      AHW     < , , >에 의하여 데이터가 할당됩니다.

*. 디버거의 G 커멘드는 코드세그머트에 대하여
           D 커멘드는 데이터 세그먼트를 기초로 하여 오프셋 번지를 상용하도록
                      되어있습니다.
예제) MUL3.ASM
벡터의 내적 계산
CODE    SEGMENT
        ASSUME  CS:CODE, DS:CODE
;
        MOV     AX, CODE   --+DS설정
        MOV     DS, AX     --+
        MOV     AL, VCTA.X
        IMUL    VCTB.X
        MOV     BX, AX
        MOV     AL, VCTA.Y
        IMUL    VCTB.Y
        ADD     BX, AX
        MOV     AL, VCTA.Z
        IMUL    VCTB.Z
        ADD     BX, AX
        MOV     ANS.BX
;
        MOV     AH,4CH
        INT     21H
;
ANS     DW?
;
VCT     STRUC
X       DB?      ; X, Y, Z는 필드 이름
Y       DB?
Z       DB?
VCT     ENDS
;
VCTA    VCT     <1,-2,3>

VCTB    VCT     <2,3,-1>
;
CODE    ENDS
        END


5.11 나눗셈 명령
--------------------------------------------------------------------------------
나눗셈 명령에는 부호 없는 나눗셈을 하는 DIV(divide) 명령과 , 부호가 붙은  나눗셈을 
하는 IDIV(Integer divide) 명령 등이 있습니다. 나눗셈은 기본적으로는 곱셈 명령의 역함
수이므로 곱셈 명령으로부터 어느 정도 사용법 등을 생각해 낼 수가 있습니다.
정수끼리 나눗셈을 하였을 때, 일반적으로 깨끗이 나눠져 결과가 얻어진다. 고  말할 수 없
입니다. 그래서 8086 CPU에서는 나눗셈의 결과는 몫과 나머지로 나누어 2개의  레지스터
에 저장합니다.
+--------------------------------------------------------------------+
|16비트 % 8비트                                                      |
|     피제수               젯수                나머지    몫          |
|     +---+    +----------------------------+   +---+   +---+        |
|     |AX |  % |8비트의 레지스터 혹은 메모리 |-->|AH |   |AL |        |
|     +---+    +----------------------------+   +---+   +---+        |
|                                                                    |
| 32비트 % 16비트                                                    |
|     피제수                제수                  나머지   몫        |
|  +----+---+   +-----------------------------+    +---+  +---+      |
|  |DX  |AX | % |16비트의 레지스터 혹은 메모리 |--->|DX | |AX  |      |
|  +----+---+   +-----------------------------+    +---+  +---+      |
+--------------------------------------------------------------------+
 이경우의 DX:AX레지스터 표현은 지금까지 와 마찬가지로 32비트 레지스터로서 사용돼
고 있습니다. 결과가 몫과 나머지라는 형태로  된다는 것 외에는 , 곱셈의  역연산(逆演
算)이라고 생각하면 외우기 쉬우리라고 생각됩니다.
여기서 주의하지 않으면 안 되는 것은 부호가 있는 나눗셈의 경우로서 , 음수를 포함하는 
나눗셈일 경우의 나머지 부호는 어떻게 할 것인가 라는 사항인데 , 8086 CPU에서 는 피젯
수의 부호와 나머지의 부호가 일치하는 결과를 구하도록  되어있습니다.
사용법:
DIV +--범용 레지스터 (8/16 bit)
    +--메모리        (8/16 bit)
IDIV +--범용 레지스터 (8/16 bit)
     +--메모리       (8/16 bit)
명령의 사용법은 곱셈 명령과 비숫해서 피제수를 미리 AX 레지스터 혹은 DX:AX  레지스
터에 저장한 다음에 오퍼랜드를 제수가 들어간 레지스터 혹은 메모리에 지정합니다.
16비트 % 8비트 인지 , 32비트 % 16비트인지는 제수의 지정에 의한 레지스터 혹은 메모
리의 크기에 따라 결정됩니다.

0에 의한  나눗셈의 처리 :
여기서 문제 되는 것은 , 0에 의한 나누세요과 오버플로우(overflow, 자리 넘침)인 경우 입니
다. 나눗셈에 제수로서 0을 지정한 경우 , 나눗셈을 할 수가 없습니다. 이와 같은 경우에
는 명령 실행을 중단하고 INT 0 인터럽트를 발생하여 0에 의한 나눗셈 처리 루틴으로 
실행을 옮깁니다.   [인터럽트란 일종의 서브루틴 호출을 말합니다.]
 INT 0 인터럽트는 0 에 의한 나눗셈을   실행할 때에 CPU 가 자동적으로 개입하여 실
행하는 특수한 인터럽트입니다. 이 루틴 중에 0으로 나눈 경우의 처리법을 미리 작성해
둡니다.(보통은 O.S. 등이 설정해 둔다.)
또한 나눗셈을 실행할 때에 오버플로우가  발생하는 경우가 있습니다. 이것은  나눗셈의 
결과를 저장하는 레지스터의 크기가 정해져  있기 때문에 , 예를 들면  16비트%8비트에 
있어서                         FFFF%1
 
                                                                                                                                                                                  ---> FFFF
 로된경우, 결과가 8비트로 된 AL 레지스터에 들어가지 않으므로 오버플로우가  발생합
니다. 이와 같은 경우도 8086에서는 INT 0를 발생하여 실행을 중단합니다.

 

예제) DIV1.ASM
삼각형의 중심을 구하는 문제(나눗셈 설명용)
CODE    SEGMENT
        ASSUME  CS:CODE, DS:DATA
        MOV     AX, DATA   -+DS 설정
        MOV     DS, AX     -+
;
        MOV     AL, A.X
        CBW              ;AL을 AX로 부호 확장
        MOV     BX, AX
        MOV     AL, B.X
        CBW
        ADD     BX, AX
        MOV     AL, C.X
        CBW
        ADD     AX, BX
        MOV     BH,3
        IDIV    BH   ; 3으로 나눈다
        MOV     M.X, AL
;
        MOV     AL, A.Y
        CBW
        MOV     BX, AX
        MOV     AL, B.Y
        CBW
        ADD     BX, AX
        MOV     AL, C.Y
        CBW
        ADD     BX, AX
        MOV     AL, C.Y
        CBW
        ADD     AX, BX
        MOV     BH,3
        IDIV    BH
        MOV     M.Y, AL
;
        MOV     AH,4CH
        INT     21H
;
CODE    ENDS
;-----------------
DATA    SEGMENT
P       STRUC
X       DB?
Y       DB?
P       ENDS
;
A       P       <12,41>
B       P       <-53,-19>
C       P       <?,?>
;
DATA    ENDS
        END

예제) DIV2.ASM
초를 시간 , 분, 초로 바꾸는 문제
CODE    SEGMENT
        ASSUME  CS:CODE, DS:DATA
SIXTY   EQU     60
;
        MOV     AX, DATA
        MOV     DS, AX
;
        MOV     SI, OFFSET SECOND
        MOV     AX, [SI]
        MOV     DX, [SI+2]
        MOV     BX, SIXTY
        DIV     BX
        MOV     TIME 2.SEC, DL
        MOV     BL, SIXTY
        DIV     BL
        MOV     TIME 2.MIN, AH
        MOV     TIME 2.HOUR, AL
;
        MOV     AH,4CH
        INT     21H
;
CODE    ENDS
;------------------
DATA    SEGMENT
TIME    STRUC
HOUR    DB?
MIN     DB?
SEC     DB?
TIME    ENDS
;
SECOND  DD      72912
TIME 2   TIME    < , , >

DATA    ENDS
        END

 주의해야 할 점은 , DD에 의해 정의된 숫자는 메모리상에서 는
1바이트씩 완전하게 순서로 나열된다는 것입니다.
72912(10진수)=         00 01 1C D0 ( 16진수)
메모리 상의 데이터      D0 1C 01 00
                       --+-   -+--
    하위 16비트(역 워드) <-+     +->  상위 16비트(역 워드)

'어셈블리' 카테고리의 다른 글

어셈블리 강좌6  (0) 2019.07.09
어셈블리 강좌5  (0) 2019.07.09
어셈블리 강좌3  (0) 2019.07.09
어셈블리 강좌2  (0) 2019.07.09
어셈블리 강좌1  (0) 2019.07.09