이번호에서는 우리가 일상 접하는 사칙연산이 어셈블리 언어라는 프로그래밍 언어에서는 어떤 형태로 구현이 되는가, 그리고 이와 더불어 프로그램을 작성하는 경우에 없어서는 안될 비교분기에 대해 문법적이 내용들을 공부해 보기로 하자.
산술연산
일반 고급언어를 사용해서 프로그램을 작성하는 경우에는 산술연산이라는 것에 대해서 별로 신경쓸 일이 없다. 하지만 어셈블리 언어를 사용해서 산술연산을 수행하는 경우에는 그런 일반의 고급언어와는 달리 주의를 요하는 것이 많다. 때문에 간단한 연산의 경우에는 어셈블리 언어를 이용하는 경우도 꽤 있지만, 전문적인 계산을 필요로 하는 프로그램에서 어셈블리 언어를 이용하는 경우는 그리 많지 않다. 여기에서도 이런 흐름에 발맞추어서 기본적인 산술연산만을 설명하기로 한다. 그리고 특별히 복잡한 산술연산을 필요로 하는 사람들을 위해서 C 언어와 어셈블리 언어의 인터페이스에 대해서도 자세히는 아니지만 구현이 가능할 정도까지는 다루어 볼 생각이다.
시작하기 전에
우선 산술연산 명령에 들어가기에 앞서 어셈블리 언어에서의 문법적인 규칙을 몇가지 알아보기로 하자.
1.라벧
프로그램이 어느 특정한 위치를 가리키기 위해서 베이직과 같은 언어에서는 문번호라는 것을 사용한다. 그래서 프로그램의 제어를 어느 특정한 위치로 강제로 옮기는 경우에는,
100
200 GOTO 100
형태의 GOTO 명령을 사용한다. 여기서 100, 200은 프로그램의 어느 특정한 위치를 가리키는 문번호( 어셈블리 언어에서는 라벧이 사용된다. )를 의미하고 GOTO는 프로그램의 제어를 어느 특정한 위치로 옮기는데 사용되는 점프명령 (어셈블리 언어에서는 분기명령이 사용된다)이 되는 것이다. 베이직의 문번호와 같은 의미를 가지는 라벧을 사용하고자 할때는 먼저 원하는 벧의 이름을 쓰고 라벧의 끝에 반드시 :을 붙여야만 한다. :을 반드시 붙여야 하는 이유는 컴파일러에게 이것은 라벧이라는 의미를 잔달하기 위해서 이다. 예를 들면 다음과 같다.
( 예 )
WelcomeTo:
Come_Here:
This_is_a_Label:
2.명령어와 오퍼랜드
명령어는 어떤 동작을 하도록 지시하는 것을 일컫는다. 예를 들어서 데이터를 옮기는데 사용되는 MOV, 그리고 인터럽트를 부르는 INT, 그리고 이번 호에서 다룰 사칙연산에 사용되는 ADD, SUB, MUL, DIV 등이 바로 그것이다. 그리고 사용되는 명령어에 따라서 명령어의 오른쪽에 오는 부분이 결정되는데, 이 부분을 가리켜서 우리는 오퍼랜드Operand라고 한다. 즉, 오른쪽에서 가장 먼저 오는 부분은 제 1 오퍼랜드, 두번째 오는 부분은 제 2 오퍼랜드 등으로 나타낸다.
( 예 ) MOV AX,BX
— — —
| | |
| | +– 제 2 오퍼랜드
| +—– 제 1 오퍼랜드
+————- 명 령 어
INC CX
— —
| |
| +—– 제 1 오퍼랜드
+————- 명 령 어
위의 예에서 보는 것처럼 오퍼랜드 사이의 구분은 ,를 사용한다. 그리고 필요에 따라서는 제 1, 제 2 오퍼랜드의 뒤에 ;를 사용하고 필요한 내용을 추가할 수도 있다. 이는 원시 프로그램에서만 의미가 있고, 컴파일의 단계를 거치게 되면 없는 것이나 마찬가지이다.
* 더하기
두 오퍼랜드의 내용을 더하는 ADD 명령의 형식은 제 1 오퍼랜드와 제 2 오퍼랜드의 조합에 의해서 여러가기의 형태가 있을 수 있다.
+- 레지스터( 8/16 Bit ) -+ +- 레지스터( 8/16 Bit ) -+
ADD | | | 메 모 리( 8/16 Bit ) |
+- 메 모 리( 8/16 Bit ) -+,+- 숫 자( 8/16 Bit ) -+
( 예 ) ADD AX,BX ( O )
ADD AX,WORD PTR ES:[BX] ( O )
ADD AX,0FFFFH ( O )
ADD WORD PTR ES:[BX],AX ( O )
ADD WORD PTR ES:[SI],WORD PTR DS:[DI] ( X )
ADD WORD PTR ES:[BX],0FFFFH ( O )
하지만 실제 사용할 때에는 모든 조합들 중에서 제 1, 제 2 오퍼랜드가 메모리를 취하는 5 번째와 같은 형태는 사용할 수 없다. 그리고 제 1 오퍼랜드는 연산한 결과를 가지므로 숫자의 형태가 올 수 없다.
* 빼기
빼기 역시 더하기와 마찬가지로 두개의 오퍼랜드을 대상으로 해서 제 1 오퍼랜드의 내용에서 제 2 오퍼랜드의 내용을 빼고 그 결과를 제 1 오퍼랜드에 저장하는 명령이다.
+- 레지스터( 8/16 Bit ) -+ +- 레지스터( 8/16 Bit ) -+
SUB | | | 메 모 리( 8/16 Bit ) |
+- 메 모 리( 8/16 Bit ) -+,+- 숫 자( 8/16 Bit ) -+
( 예 ) SUB AX,BX ( O )
SUB AX,WORD PTR ES:[BX] ( O )
SUB AX,0FFFFH ( O )
SUB WORD PTR ES:[BX],AX ( O )
SUB WORD PTR ES:[SI],WORD PTR DS:[DI] ( X )
SUB WORD PTR ES:[BX],0FFFFH ( O )
* 곱하기
위에서 알아본 더하기와 빼기는 몇몇 레지스터를 제외하고는 어떤 것이든 이용이 가능하다. 하지만 지금부터 다룰 곱하기와 나누기에서는 사용되는 레지스터가 제약된다. 먼저 곱하기를 살펴보면, 아래에서 보는 것처럼 두가지 형태의 것이 있다. 두가지 중에서 어떤 것을 사용할 것인가는 곱셈에 사용되는 수치의 크기에 따라 좌우된다.
– 8 bit 곱셈
결과
+—-+ +————————+ +—-+
| AL | * | 8 bit Reg 혹은 Memory | = | AX |
+—-+ +————————+ +—-+
– 16 bit 곱셈
결 과
+—-+ +————————+ +—-+—-+
| AX | * | 16 bit Reg 혹은 Memory | = | DX | AX |
+—-+ +————————+ +—-+—-+
( 예 ) ;
; 8 bit 레지스터 * 8 bit 레지스터
;
MOV AL,10
MOV CL,10
MUL CL
;
; 8 bit 레지스터 * 8 bit 메모리
;
MOV AL,10
MUL BYTE PTR ES:[BX]
;
; 16 bit 레지스터 * 16 bit 레지스터
;
MOV AX,400
MOV BX,500
MUL BX
;
; 16 bit 레지스터 * 16 bit 메모리
;
MOV AX,400
MUL WORD PTR ES:[BX]
8 bit 곱하기의 경우에는 그림에서 보는 것처럼 결과가 AX 레지스터에 담기고, 16 bit 곱하기의 경우에는 DX:AX 레지스터의 조합에 담기게 된다는 것을 주지하기 바란다.
* 나누기
나눗셈 역시 곱셈의 경우와 마찬가지로 사용되는 레지스터에 제약이 있고, 연산의 결과 역시 곱셈의 경우와 마찬가지로 DX, AX 레지스터를 사용해서 저장된다.
– 16 bit 8 bit
나머지 몫
+—-+ +————————+ +—-+ +—-+
| AX | * | 8 bit Reg 혹은 Memory | = | AH | | AL |
+—-+ +————————+ +—-+ +—-+
– 32 bit 16 bit
나머지 몫
+—-+—-+ +————————+ +—-+ +—-+
| DX | AX | * | 16 bit Reg 혹은 Memory | = | DX | | AX |
+—-+—-+ +————————+ +—-+ +—-+
( 예 ) ;
; 16 bit 레지스터 8 bit 레지스터
;
MOV AX,100
MOV CL,10
DIV CL
;
; bit 레지스터 8 bit 메모리
;
MOV AX,100
DIV BYTE PTR ES:[BX]
;
; 32 bit 레지스터 16 bit 레지스터
;
MOV DX,300
MOV AX,400
MOV BX,500
DIV BX
;
; 32 bit 레지스터 16 bit 메모리
;
MOV DX,300
MOV AX,400
DIV WORD PTR ES:[BX]
나눗셈의 결과는 나머지와 몫의 두가지가 있다. 16 bit 8 bit의 경우에는 AH 레지스터에 나머지가, 그리고 AL 레지스터에 몫이 담기고, 32 bit 16 bit의 경우에는 DX 레지스터에 나머지가 그리고 AX 레지스터에 몫이 담긴다는 것을 기억하기 바란다.
* 데이터의 교환
이번에는 지금까지의 사칙연산과는 조금 동떨어진 내용이기는 하지만 데이터 교환의 효율이라는 관점에서 없어서는 안될 데이터 교환 명령에 대해서 알아보기로 하자.
+- 레지스터( 8/16 Bit ) -+ +- 레지스터( 8/16 Bit ) -+
XCHG | | | |
+- 메 모 리( 8/16 Bit ) -+,+- 메 모 리( 8/16 Bit ) -+
데이터 교환 명령은 제 1, 제 2 오퍼랜드의 내용을 서로 교환하는 명령이므로 양쪽 오퍼랜드 모두가 데이터를 담을 수 있는 형태이어야 하므로 어느 오퍼랜드도 숫자를 가질 수는 없다. 아래의 에서는 XCHG 명령어를 사용하지 않고 데이터 교환을 구현한 것이고, 는 XCHG 명령을 사용하여 데이터를 교환한 것이다.
XCHG 명령어를 사용하지 않은 경우
( 예 ) ;
; AX 레지스터와 BX 레지스터의 내용을 교환
;
MOV CX,AX
MOV AX,BX
MOV BX,CX
;
; AX 레지스터와 메모리 내용을 교환( 레지스터와 메모리 )
;
MOV BX,AX
MOV AX,WORD PTR ES:[BX]
MOV WORD PTR ES:[BX],AX
XCHG 명령어를 사용한 경우
( 예 ) XCHG AX,BX
XCHG AX,WORD PTR ES:[BX] ( O )
XCHG WORD PTR ES:[BX],AX ( O )
비교분기
지금까지 우리는 데이터의 전송, 인터럽트, 사칙연산 등에 대해서 알아보았다. 그러나 실제로 프로그램을 작성하다 보면 이것만으로는 불충분하다. 여기서는 이를 보완하기 위해서 프로그램의 흐름을 제어하는 분기명령에 대해서 알아보고자 한다. 이 분기명령을 이용하면 프로그램의 반복이 가능해진다.
* 조건 분기명령
분기명령은 어떤 조건을 부여하고 그 조건의 만족 여부에 의해서 분기를 달리하고자 하는 경우에 사용되는 조건분기와, 조건에 상관없이 무조건적으로 프로그램의 어느 특정 부분으로 제어를 옮기고자 하는데 사용되는 무조건분기로 나뉘어진다. 이 두가지의 분기 중에서 먼저 어떤 조건을 부여하고 그 조건이 만족되는가/그렇지 않는가에 따라서 분기를 달리하는 조건분기에 대해서 알아보기로 하자. 이 조건분기는 크게 두 부분으로 나뉘어진다. 하나는 어떤 조건을 부여하는 부분, 그리고 다른 하나는 부여된 조건에 의해서 세팅된 플래그 레지스터의 내용을 이용해서 프로그램의 제어를 옮기는 제어부분이다.
CMP +- 레지스터( 8 bit/16 bit ) -+ +- 레지스터( 8 bit/16 bit ) -+
| | | 메 모 리( 8 bit/16 bit ) |
+- 메 모 리( 8 bit/16 bit ) -+, +- 숫 자( 8 bit/16 bit ) -+
JE To_Anywhere
— ———–
| |
| +——- 제어를 이동할 위치의 라벧
+————————– 여러가지 조건 비교의 종류들
조건을 부여하는 부분에서는 제 1 오퍼랜드와 제 2 오퍼랜드의 비교를 행하고 그 결과를 플래그 레지스터에 세팅한다. 그러면 제어부분에서 사용된 비교의 결과가 참이면 제 1 오퍼랜드에 쓰여져 있는 라벧로 제어를 옮기고, 만약 거짓이 되면 바로 다음의 명령으로 제어를 넘기게 된다.
[표 1] 조건 분기 명령에 대한 일람표
[표 2] 조건 분기 명령의 약자를 읽는 방법
<표 1>에서는 조건 비교의 종류를 정리하여 실었다. 여기서 알 수 있는 것처럼 조건 비교는 부호가 있는 것인가. 없는 것인가에 의해서 크게 두 종류의 형태로 구별된다.
* 무조건 분기명령
무조건 분기는 조건분기와는 달리 어떤 조건이 주어졌느냐에 관여치 않고, 제 1 오퍼랜드에서 지정된 라벧의 위치로 무조건 제어를 옮기고자 하는 경우에 사용된다. 이에 사용되는 명령어는 JMP 이다.
JMP To_Anywhere
— ———–
| |
| +——- 제어를 이동할 위치의 라벧
+————————– 무조건 분기 명령어
* 분기명령을 이용한 반복기법
이제는 비교분기와 무조건 분기를 이용해서 반복을 행할 수 있게 하는 간단한 방법을 구현해 보기로 하자. CX 레지스터에 주어진 회수만큼 반복을 행하는 예이다.
MOV CX,Pepeat_Count
Routine_In: CMP CX,0
JE Routine_Out
;
; 반복되는 부분
;
DEC CX
JMP Routine_In
Routine_Out:
물론 위에서 예로 제시한 반복의 경우는 이 방법이 아니더라도 여러가지 형태로 구현이 가능하다.
* 비교분기와 플래그 레지스터
일반적인 레지스터는 8 bit 혹은 16 bit의 크기로 동작하는데 비해서, 이 플래그 레지스터는 각각의 비트 하나하나가 의미를 가지고 독립적으로 동작한다. 이 각각의 비트가 가지는 의미는 다음과 같다.
15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
+–+–+–+–+–+–+–+–+–+–+–+–+–+–+–+–+
| | | | |OF|DF|IF|TF|SF|ZF| |AF| |PF| |CF|
+–+–+–+–+–+–+–+–+–+–+–+–+–+–+–+–+
| | | | | | | | |
| | | | | | | | |
| | | | | | | | +- 캐 리 플래그
| | | | | | | +——- 패 리 티 플래그
| | | | | | +————- 보조캐리 플래그
| | | | | +——————- 제 로 플래그
| | | | +———————- 사 인 플래그
| | | +————————- 트 랩 플래그
| | +—————————- 인터럽트 플래그
| +——————————- 디 렉 션 플래그
+———————————- 오버플로 플래그
(공백인 비트는 사용하지 않는다)
1) 캐리 플래그
최상의 비트로부터의 높은 자리로 자리올림 혹은 최상의 비트로부터의 빌림이 발생한 경우에 1로 세트되고, 그 이외의 경우에는 0으로 세트된다.
2)패리티 플래그
연산한 결과 1로 된 비트의 개수가 짝수개 일 때는 1로 세트되고, 홀수개 일 때는 0으로 세트된다.
3)보조 캐리 플래그
8(16)비트 연산에서, 하위 4(8)비트로부터 상위 4(8)비트로의 자리올림 혹은 빌림이 발생한 경우에 1로 세트되고 그 이외의 경우에는 0으로 세트된다. 10진 보정 명령에 있어서도 사용된다.
4)제로 플래그
연산한 결과가 0으로 되었을 때 1로 세트되고, 그 이외의 경우에는 0으로 세트된다.
5)사인 플래그
연산한 결과 최상위 비트가 1이 되었을 때 1로 세트되고, 그 이외의 경우에는 0으로 세트된다.
6)트랩 플래그
이 플래그가 0일때는 CPU는 보통의 상태처럼 동작한다. 그러나 이 플래그가 1로 세트되었을 때는 CPU는 한 명령이 실행될 때마다 자동적으로 내부 인터럽트를 발생하고, 인터럽트 처리루틴으로 들어간다. 프로그램의 추적에 사용된다.
7)인터럽트 플래그
이 플래그가 0일때는 INTR 단자로부터의 외부 인터럽트 요구는 무시되고, 1일때는 외부 인터럽트 요구를 받아들인다.
8)디렉션 플래그
스트링을 조작하는 경우에 이 플래그가 0이면 번지를 나타내는 레지스터의 값이 자동적으로 증가하고, 1이면 자동적으로 감소한다.
9)오버플로 플래그
연산을 부호가 달린 숫자로 했을 때 오버플로 혹은 언더플로가 발생한 경우에 1로 세트되며, 그 이외의 경우에는 0으로 세트된다.
* 또다른 명령들
프로그램을 작성하다 보면 어떤 특정한 레지스터나 메모리의 값을 1 씩 증가시키거나 감소시키는 경우가 빈번히 생긴다. 이런 경우에 사용되는 INC, DEC 명령어, 그리고 메모리의 확보를 위해서 사용되는 DUP 의사명령에 대해서 알아보기로 하자.
1)INC 명령
INC 명령은 레지스터나 메모리의 내용을 하나 증가시키는 경우에 사용되는 명령어이다.
INC AX
— —
| |
| +– 증가할 레지스터/메모리
+—————– 증가 명령어
2)DEC 명령
DEC 명령은 레지스터나 메모리의 내용을 하나 감소시키고자 하는 경우에 사용되는 명령어이다.
DEC AX
— —
| |
| +– 감소할 레지스터/메모리
+—————– 감소 명령어
3)DUP 의사명령
DUP 의사명령은 동일한 값으로 여러개의 데이터를 연속해서 지정하거나 어떤 특정한 값으로 데이터를 지정하지 않고 단지 공간만을 확보하고자 하는 경우에 사용되는 의사명령어이다.
Test_Buffer DB 10 DUP(?)
———– — — — –
| | | | |
| | | | |
| | | | +- 반복할 코드값
| | | +— 반복 의사명령
| | +——- 변수의 길 이
| +————— 변수의 형
+————————– 변수의 이 름
C 언어와의 인터페이스
C 언어와 어셈블리 언어를 인터페이스 할 수 있는 방법에는 두가지가 있다. 하나는 C 언어의 소스 프로그램을 작성하는 단계에서 인라인 어셈블을 사용하여 하나의 소스 프로그램 안에 C 언어와 어셈블리 언어를 동시에 사용하는 방법이고, 다른 하나는 C 언어와 어셈블리 언어 각각의 소스 파일을 작성하고, 각각의 컴파일러를 거쳐서 만들어진 목적 파일의 형태에서 링커를 사용하여 하나의 실행 파일로 만드는 방법이다. <그림 1>에서는 후자에 대한 자세한 단계를 나타냈다.
+——————–+ +———————-+
| C 언어 소스 파일 | | 어셈블리 소스 파일 |
| C_SOURCE.C | | A_SOURCE.ASM |
+———+———-+ +———–+———-+
| |
| C 컴파일러 | 어셈블리 컴파일러
| |
+———+———-+ +———–+———-+
| C 언어 목적 파일 | | 어셈블리 목적 파일 |
| C_SOURCE.OBJ | | A_SOURCE.OBJ |
+———+———-+ +———–+———-+
| |
| 두언어의 링크 |
+————–+—————-+
|
|
+———+———-+
| C 언어 목적 파일 |
| C_SOURCE.EXE |
+——————–+
[그림 1] C 언어와 어셈블리 언어의 인터페이스
여기에서는 두가지의 방법 중에서 후자의 방법을 사용하여 두 언어 사이의 인터페이스가 가능하도록 할 생각이다.
* C 언어와 어셈블리 언어 소스 프로그램
먼저 C 언어와 어셈블리 언어의 인터페이스를 구현한 간단한 예제가 <리스트 1>과 <리스트 2>이다.
/*
<><><><><><><><><><><><><><><><><><><><><><><><><><><><>
<> C 언어와 어셈블리 언어의 인터페이스 예 <>
<><><><><><><><><><><><><><><><><><><><><><><><><><><><>
<> 컴파일과 링크는 <>
<> <>
<> TCC -c -ml C_SOURCE <>
<> TASM A_SOURCE <>
<> TLINK c0l C_SOURCE A_SOURCE,DEMO,DEMO,cl <>
<><><><><><><><><><><><><><><><><><><><><><><><><><><><>
*/
/*
;
; 어셈블리 언어에서 사용되는 함수의 선언
;
*/
extern int Adder(int x,int y);
extern void Sprint(char *str);
void main()
{
char *Demo_Msg = “Copyright (C) 1992-01-22 By Wild Horse Software
Reserach !!!$”;
printf(\n[=> C & Assembler Interface <=]\n);
/*
;
; Adder 함수를 테스트한다.
;
*/
printf(\nAdder Routine : 35 + 27 = %d\n,Adder(35,27));
printf(Sprint Routine : );
/*
;
; Sprint 함수를 테스트한다.
;
*/
Sprint(Demo_Msg);
printf(\n);
}
[ 리스트 1 ] C 언어로 작성된 메인 함수
;
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> C 언어와 어셈블리 언어의 인터페이스 예 <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;<> 컴파일과 링크는 <>
;<> <>
;<> TCC -c -ml C_SOURCE <>
;<> TASM A_SOURCE <>
;<> TLINK c0l C_SOURCE A_SOURCE,DEMO,DEMO,cl <>
;<><><><><><><><><><><><><><><><><><><><><><><><><><><><>
;
ASM_Routine segment byte public ‘CODE’
assume cs:ASM_Routine,ds:ASM_Routine
;
; 입력 : 16 bit의 정수, 16 bit의 정수
; 리턴 : 16 bit의 정수
;
public _Adder
_Adder proc far
push bp
mov bp,sp
mov ax,word ptr [bp+6] ; 첫번째 16 bit
add ax,word ptr [bp+8] ; 두번째 16 bit
pop bp
ret
_Adder endp
;
; 입력 : 문자열의 번지
; 리턴 : 없음
;
public _Sprint
_Sprint proc far
push bp
mov bp,sp
mov dx,word ptr [bp+8] ; 문자열의 세그먼트
mov ds,dx
mov dx,word ptr [bp+6] ; 문자열의 오프셋
mov ah,9 ; 문자열 출력
int 21h
pop bp
ret
_Sprint endp
ASM_Routine ends
end
[ 리스트 2 ] 어셈블리 언어로 작성된 서브 함수
* C 언어에서의 어셈블리 함수 선언
<리스트 1>에서 볼 수 있는 것처럼 C 언어에서는 어셈블리 언어와 인터페이스된 함수임을 선언하기 위해서 ‘extern’이라는 명령어를 사용한다. 사용되는 형태는 다음과 같다.
extern int Adder(int x, int y)
—— — —– —– —–
| | | | |
| | | | +—- 두번째 전달인수
| | | +———– 첫번째 전달인수
| | +—————– 함수의 이름
| +———————- 함수의 리턴형을 선언
+—————————- 외부함수임을 선언
* 어셈블리 언어에서의 함수 선언
<리스트 2>에서 볼 수 있는 것처럼 어셈블리 언어에서도 C 언어에서 불리어질 함수의 선언을 필요로 한다. 이에 사용되는 명령어는 ‘public’이다. 사용되는 형태는 다음과 같다.
public _Adder
—— ——
| |
| +—- 외부에서 불리어질 함수의 이름
+———– 외부에서 불리어질 함수임을 선언
여기서 주의해야 할 것은 C 언어는 어셈블리 언어를 사용해서 작성된 외부 함수의 이름 앞에 반드시 ‘_’를 붙여야만 인식할 수 있다는 것이다. 만약 ‘_’이 없다면 C 언어는 어셈블리 언어로 작성된 서브함수를 인식할 수 없다.
* C 언어의 Large Model에서 있어서의 인수전달
이제 두 언어 사이에 어떤 형태로 인수가 전달되는가를 간단히 살펴보기로 하자. C 언어에서는 여러가지의 메모리 모델이 있다. 때문에 우리가 사용하는 스택의 전달 방법이 다른 메모리 모델에서는 성립되지 않기도 하므로 반드시 메모리 모델을 지켜야만 한다.
1)수치값의 전달
C 언어는 스택을 사용해서 어셈블리 언어로 인수를 전달한다. 이 스택의 전달이 C 언어와 어셈블리 언어 인터페이스의 핵심이 된다.
+————+
SP | IP | -+
+————+ | 프로그램 내부에서 일어남
SP+2 | CS | -+
+————+
SP+4 | 인수 1 | int x
+————+
SP+6 | 인수 2 | int y
| |
(예)
MOV AX,WORD PTR [BP+4] ; AX Reg에 int x의 값을 전송
ADD AX,WORD PTR [BP+6] ; AX Reg에 int y의 값을 더함
그림에서 볼 수 있는 것처럼 C 언어는 어떤 서브루틴으로 제어를 옮기는 경우에 서브루틴을 끝내고 원래의 위치로 돌아오기 위해서 현재 위치의 CS와 IP도 스택에 PUSH하여 놓기 때문에 우리가 원하는 인수는 현재 스택의 위치에서 4를 더한 BP+4에 위치하게 된다. 참고로 우리가 만든 프로그램에서는 SP의 값을 BP로 옮기기 전에 BP의 값을 스택에 PUSH 하였기 때문에 BP+4의 위치에서 2를 더한 BP+6이 첫번째 인수의 위치가 되는 것이다.
2)문자열의 전달
C 언어에서의 문자열을 char 형의 단일문자가 연속된 것이므로 이를 어셈블리 언어의 서브함수로 전달하기 위해서는 수치값의 전달 형태를 가지고는 불가능하다. 그래서 C 언어의 Large Model에서는 문자열 인수인 경우에는 그 문자열이 위치하고 있는 세그먼트와 번지(오프셋)을 함께 전달해 주는 방법을 사용한다. 이 내용을 위의 경우처럼 표현하면 다음과 같다.
+—————+
SP | IP | -+
+—————+ | 프로그램 내부에서 일어남
SP+2 | CS | -+
+—————+
SP+4 | 인수 오 프 셋 | -+
+—————+ | 문자열 인수의 전달
SP+6 | 인수 세그먼트 | -+
| |
(이용 예)
MOV AX,WORD PTR [BP+6]
MOV DS,AX
MOV DX,WORD PTR [BP+4]
그리고 Adder의 경우처럼 어셈블리 언어에서 C 언어로 어떤 수치값을 리턴하고자 하는 경우에는 AL( 전달값이 8 bit ), AX( 전달값이 16 bit ), DX:AX( 전달값이 32 bit )를 사용한다.
* C 언어와 어셈블리 언어로 작성된 파일의 컴파일과 링크
C 언어와 어셈블리 언어를 사용해서 작성된 각각의 소스 파일을 컴파일하고 링크하는 과정에 대해서 간단히 알아보기로 하자.
1)C 언어 소스의 컴파일
C 언어로 작성된 소스 프로그램을 컴파일하기 위해서는 두가지의 옵션이 필요하다. 하나는 메모리 모델을 Large Model로 설정해 주는 -ml, 다른 하나는 컴파일만을 하도록 하는 -c가 바로 그것이다.
TCC -c -ml C_SOURCE
만약 에러가 없다면 우리가 원하는 목적 파일인 C_SOURCE.OBJ라는 파일이 생성되었을 것이다.
2)어셈블리 언어 소스의 컴파일
어셈블리 소스를 컴파일 하는 방법은 이미 우리가 알고 있는 방법과 동일하다.
TASM A_SOURCE
3)두 목적 파일의 링크
이제 1과 2에 의해서 만들어진 두개의 목적 파일을 하나로 묶는 링크의 단계만이 남았다. 이 링크의 단계에서는 조금 주의를 요한다.
TLINK c0l A_SOURCE C_SOURCE,DEMO,DEMO,cl
–+– ———+———– –+- –+- -+
| -+- —+-+– —-+— | | |
| | | | | | | +- 라이브러리 파일의 이름
| | | | | | +—– MAP 파일의 이름
| | | | | +———- 실행 파일의 이름
| | | +——-+—————– 목적 파일의 이름
+—-+—–+———+—————– 링크 프로그램
| | +—————– C 언어의 목적 파일
| +————————— 어셈블리 목적 파일
+——————————— Large 모델의 목적파일
위에서 보인 것처럼 필요한 목적파일, 실행파일의 이름, MAP 파일의 이름, 라이브러리 파일의 이름의 순서를 따라야만 하고, 그들 각각은 ‘,’를 이용해서 구분해야 한다. 참고로 여기서 c0l과 cl은 C 언어의 Large Model에서 사용하는 목적 파일과 라이브러리 파일이다. 이상으로 사칙연산과 비교분기에 관한 설명을 마친다. 참고로 궁금한 점이 있는 독자는 케텔 아이디 ‘HIDONGII’로 문의하기 바란다.

PC 어셈블러 