1 올리기, 1 내리기, 더하기, 빼기, 부호 바꾸기의 다섯 명령이다. 계산기의 버튼처럼 한 번에 한 가지만 하고, 결과는 늘 받는 쪽에 덮어쓴다. 그래서 긴 식은 한 줄에 못 쓰고 작은 계산 여러 줄로 쪼개야 한다. 계산은 명령에 적은 칸의 크기 안에서만 일어나므로, 칸이 넘쳐도 옆 칸으로 올림이 넘어가지 않는다.
예시로 보기
Rval = -Xval + (Yval - Zval)을 C 컴파일러는 알아서 기계어로 바꿔 준다. 어셈블리에는 식을 통째로 받는 명령이 없다. 사람이 손으로 계산하는 순서대로 한 줄씩 쓴다.
.data
Rval DWORD ?
Xval DWORD 26
Yval DWORD 30
Zval DWORD 40
.code
mov eax,Xval
neg eax ; EAX = -26
mov ebx,Yval
sub ebx,Zval ; EBX = -10
add eax,ebx
mov Rval,eax ; -36
변수 값을 레지스터로 가져와 거기서 계산하고, 다 되면 결과 변수에 적는다. Xval, Yval, Zval은 바뀌지 않는다.
정의
| 명령 | 하는 일 | 피연산자 |
|---|---|---|
INC 목적지 |
목적지 ← 목적지 + 1 | 레지스터나 메모리 |
DEC 목적지 |
목적지 ← 목적지 − 1 | 레지스터나 메모리 |
ADD 목적지,원본 |
목적지 ← 목적지 + 원본 | MOV와 같은 규칙 |
SUB 목적지,원본 |
목적지 ← 목적지 − 원본 | MOV와 같은 규칙 |
NEG 목적지 |
목적지의 부호를 바꾼다 | 레지스터나 메모리 |
화살표 ←는 “오른쪽을 계산해 왼쪽에 넣는다”다. ADD와 SUB가 MOV와 같은 규칙을 따른다는 말은, 두 피연산자의 크기가 같아야 하고 둘 다 메모리일 수 없다는 뜻이다: 데이터 전송 명령.
NEG는 2의 보수를 구한다. 비트를 모두 뒤집고 1을 더한 값이고, 0에서 그 값을 뺀 것과 같다1.
예제
INC와 DEC.
.data
myWord WORD 1000h
myDword DWORD 10000000h
.code
inc myWord ; 1001h
dec myWord ; 1000h
inc myDword ; 10000001h
mov ax,00FFh
inc ax ; AX = 0100h
mov ax,00FFh
inc al ; AX = 0000h
마지막 두 경우가 갈린다. inc ax는 16비트 전체에서 00FFh + 1 = 0100h다. inc al은 아랫바이트 8비트 안에서만 FFh + 1을 하므로 AL이 00h로 돌아가고, 넘친 올림은 AH로 가지 않는다.
AH AL
mov ax,00FFh 00 FF
inc al 00 00 ← AL만 바뀐다. 올림은 버려진다
연습 문제.
.data
myByte BYTE 0FFh, 0
.code
mov al,myByte ; AL = FFh
mov ah,[myByte+1] ; AH = 00h
dec ah ; AH = FFh
inc al ; AL = 00h
dec ax ; AX = FEFFh
dec ah는 00h에서 1을 빼 FFh가 된다. 그 시점의 AX는 FF00h이고, dec ax가 16비트 전체에서 1을 빼 FEFFh가 된다.
ADD와 SUB.
.data
var1 DWORD 10000h
var2 DWORD 20000h
.code ; ---EAX---
mov eax,var1 ; 00010000h
add eax,var2 ; 00030000h
add ax,0FFFFh ; 0003FFFFh
add eax,1 ; 00040000h
sub ax,1 ; 0004FFFFh
add ax,0FFFFh는 EAX의 아래 16비트(0000h)에만 더한다. add eax,1은 32비트 전체라 FFFFh에서 생긴 올림이 윗부분으로 올라가 0004가 된다. sub ax,1은 다시 아래 16비트 안에서만 0000h − 1 = FFFFh를 하고, 윗부분 0004는 그대로다.
NEG.
.data
valB BYTE -1
valW WORD +32767
.code
mov al,valB ; AL = -1
neg al ; AL = +1
neg valW ; valW = -32767
8비트에서 −128(80h)에 NEG를 하면 80h가 그대로 나온다. +128은 8비트 부호 있는 수의 범위(−128~127)에 없기 때문이다1.
활용
- 식을 옮길 때는 괄호 안부터, 변수는 레지스터로 가져와서 계산한다. 변수에 직접
neg Yval을 하면Yval자체가 바뀐다. 연습: 산술식을 어셈블리로 옮기기 연습. - 이 명령들은 계산 결과에 따라 상태 플래그를 바꾼다. 칸이 넘쳤는지는 플래그를 봐야 안다: 산술 연산과 상태 플래그.
- 흔한 실수: 레지스터의 일부(
AL,AX)에 한 계산이 나머지 부분까지 올림을 넘겨 줄 것이라고 생각한다.
연결
- 선수: 데이터 전송 명령, 정수의 2진 표현과 2의 보수
- AL, AH, AX, EAX가 겹쳐 있는 모양: x86 레지스터
- 컴파일러는 긴 식을 “연산 하나에 결과 하나”인 줄들로 먼저 쪼갠다. 위에서 손으로 한 일이 그것이다: 3주소 코드, 단순 연산과 분기의 코드 생성
확인 문제
C1 다음을 차례로 실행할 때 줄마다 AX의 값을 쓰라. mov ax,01FFh / inc al / dec ah / dec ax / inc al
답: 01FFh → 0100h → 0000h → FFFFh → FF00h.
이유: inc al은 AL 안에서만 FFh + 1 = 00h가 되고 AH의 01은 그대로다. dec ah가 01을 00으로 만든다. dec ax는 16비트 전체에서 0000h − 1 = FFFFh다. 마지막 inc al은 다시 AL만 00h로 만들고 AH의 FF는 그대로다. 둘째 줄을 0200h로 답하면 올림이 AH로 간다고 본 것이다.
C2 EAX가 0003FFFFh일 때 add ax,1과 add eax,1의 결과가 다른 이유를 쓰고, 두 결과를 쓰라.
답: add ax,1은 00030000h, add eax,1은 00040000h.
이유: 계산은 명령에 적은 피연산자의 크기 안에서만 일어난다. ax는 아래 16비트라 FFFFh + 1의 올림이 버려지고 윗부분 0003은 그대로다. eax는 32비트 전체라 올림이 16번 비트로 올라간다.
C3 neg al이 하는 일을 SUB 명령 하나와 다른 레지스터 하나로 바꿔 쓰라. AL이 5일 때 두 방법의 결과가 같은지 확인하라.
답: mov bl,0 다음에 sub bl,al. BL에 0 − AL이 들어간다. AL = 5면 둘 다 FBh(−5)다.
이유: NEG는 0에서 그 값을 빼는 것이다. 결과가 AL이 아니라 BL에 들어간다는 점만 다르다.