외로운 Nova의 작업실

Assembly 언어 공부 - 26(double 시프트 연산자와 곱셈, 나눗셈) 본문

Programming/Assembly

Assembly 언어 공부 - 26(double 시프트 연산자와 곱셈, 나눗셈)

Nova_ 2022. 9. 6. 20:11

안녕하세요. 이번시간에는 double 시프트 연산자와 곱셈, 나눗셈에대해 배워보도록 하겠습니다.

 

- SHLD 와 SHRD

SHLD 는 shift left double 명령어로, 소스 비트와 목적지 비트를 연결해서 left 해줍니다. 예시를 한번 보겠습니다. 

.code
mov al, 11110000b
mov ah, 00001111b
SHLD ah, al, 4

위 코드를 실행하게되면 ah - al 이 연결되며 왼쪽으로 시프트 하게됩니다. 즉, 00001111(ah) - 11110000(al) 이 상태에서 왼쪽으로 시프트 하는 거죠. 연산결과는 11111111(ah) - 00000000(al) 입니다. 왜냐면 마지막 4가 4번 하라는 뜻이기 때문입니다. 여기서 소비트는 al 이며, 목적지 비트는 ah라 부릅니다.

 

SHRD는 shift right double 명령어로, 소스 비트와 목적지 비트를 연결하여 right 해줍니다. 예시를 한번 보겠습니다.

.code
mov al, 11110000b
mov ah, 00001111b
SHRD ah, al, 4

위 코드를 실행하게되면 ah - al 이 연결되며 오른쪽으로 시프트 하게됩니다. 즉, 00001111(ah) - 11110000(al) 이 상태에서 오른쪽으로 싶트 하는 거죠. 연산 결과는 00000000(ah) - 11111111(al) 입니다. 

 

- MUL 명령어와 IMUL 명령어

MUL 명령어는 부호가 없는 정수 곱셈을 해주는 명령어입니다. MUL 명령어는 피연산자의 비트 크기에따라 연산 결과가 변형됩니다. 아래는 MUL 명령어와 IMUL 명령어를 사용했을때 비트 크기에 따라 연산 결과가 변하는 표입니다.

사용되는 레지스터 곱해지는 피연산자 크기 연산 결과
AL 8bit AX
AX 16bit DX:AX
EAX 32bit EDX:EAX

위를 한가지 예시를 들어 설명해보자면 먼저, 코드를 봅시다.

.code
mov al, 08h
mov bl, 10h
mul bl

위 코드를 실행하게되면 al 에는 08D이, bl에는 16D가 들어가됩니다. 즉 16진수로 8 * 10이 실행되는 것이죠. bl을 mul 하면 bl이 8비트이기때문에 자동으로 al 과 곱해지게되고, 그 결과가 ax에 남게됩니다. ax에는 80h가 저장되어 있을 겁니다. 이때 carry 플래그는 ah 부분에 값이 0이아니라면 1로 세팅됩니다. 하지만 ax에는 0080이기 때문에 해당 예제에서 carry플래그는 0이됩니다.

 

다음은 16비트 상황일때 예제를 한번 봅시다.

.code
mov ax, 2000h
mov bx, 0100h
mul bx

위 코드를 실행하게되면 16진수상에 2000 * 100이 실행됩니다. bx는 16비트 이기때문에 연산의 결과는 DX:AX에 저장되어있습니다. DX : 0020, AX : 0000 이 연산의 결과로 저장됩니다. 이때 carry 플래그는 DX값이 0이 아니라면 1로 세팅됩니다. DX 값이 0020 이기때문에 해당 예제의 carry 플래그는 1로 셋팅됩니다.

 

다음은 32비트 상황일때 예제를 한번 봅시다.

.code
mov eax, 12345h
mov edx, 1000h
mul edx

위 코드를 실행하게되면 16진수상에 12345 * 1000 이 실행됩니다. edx는 32비트이기때문에 연산의 결과는 EDX:EAX에 저장되어있습니다. EDX : 00000000, EAX : 12345000 이 연산의 결과로 저장됩니다. 이때 carry 플래그는 EDX의 값이 0이 아니라면 1로 세팅됩니다. 해당 예제는 0이기때문에 carry 플래그는 0으로 세팅됩니다.

 

IMUL 명령어는 부호가 있는 정수 곱셈을 수행합니다. 또한 연산결과에서 피연산자의 최상위비트에서 캐리플래그가 발생하고, 최상위비트로 캐리플래그가 발생하면 0으로 셋팅되고 반대로 최상위비트에서 캐리플래그가 발생하지않고, 최상위비트로 캐리플래그가 발생하지 않으면 0으로 셋팅됩니다. 이외는 다 1로 셋팅됩니다. 즉, 오버플로우는 양수끼리의 덧셈에서 음수가 나올때, 음수끼리의 덧셈에서 양수가 나올때 셋팅됩니다. 곱셈에서는 양수끼리의 곱셈에서 음수가 나올때 음수끼리의 곱셈에서 음수가나올때 셋팅됩니다.

 

8비트 상황일때 예제를 봅시다.

.code
mov al, 48
mov bl, 4
imul bl

al에는 00110000이 들어가 있고, bl에는 00000100 이 저장되어 있습니다. 이 둘을 imul 하게되면 ax에 저장되고 ah와 al의 값을 연속해서 살펴보면 ah:al = 00000000 11000000 입니다. 피연산자인 al의 최상위 비트가 원래는 0이였지만 연산결과 1로 변경되었습니다. 이는 최상위비트로 캐리플래그가 발생된 것 입니다. 하지만, 최상위비트에서 캐리플래그가 발생되지 않았으니 오버플로우 플래그가 1로 셋팅됩니다. 이는 ah 비트도 함께 보아야함을 알려줍니다.

 

16비트 상황일때 예제를 봅시다.

.code
mov ax, 48
mov bx, 4
imul bx

ax에는 00000000 00110000 이들어가있고, bx에는 00000000 00000100이 들어가 있습니다. 이 둘을 imul 하게되면 DX:AX에 저장되고 값을 연속해서 살펴보면 DX:AX = 00000000 00000000 : 00000000 11000000 입니다. 이때 ax의 최상위비트는 0이였고 연산결과 또한 0입니다. 즉, 최상위비트로의 캐리플래그가 발생하지않았고, 최상위 비트에서도 캐리플래그가 발생하지 않았기에 오버플로우 플래그는 0이됩니다. 이는 연산의 결과를 AX비트만 봐도됨을 알려줍니다.

 

32비트 상황일때 예제를 봅시다.

.code
mov eax, +4823424
mov ebx, -423
imul ebx

eax에는 00000000 01001001 11100111 10100000 이 저장되어있고, ebx에는 11111111 11111111 11111110 01011001 이 저장되어 있습니다. 연산결과는 EDX:EAX에 저장되며 값을 연속해서 살펴보면 EDX:EAX = 11111111 11111111 11111111 11111111 : 10000110 01100011 01011101 10000000 입니다. 이때 eax의 최상위 비트값이 0에서 1로 변했음으로 최상위비트로 캐리플래그가 발생됨을 알 수 있습니다. 또한 EDX값으로 1이 넘어간것을 보아 EAX의 최상위비트에서 캐리플래그가 발생했음을 알 수 있습니다. 오버플로우 플래그는 0으로 셋팅되며, 연산의 결과를 EAX 비트만 봐도됨을 알려줍니다.

 

- DIV 명령어와 IDIV 명령어

DIV 명령어는 부호없는 나눗셈을 수행합니다. 곱셈과 마찬가지로 피연산자의 비트수에 따라 결과가 다르게 저장됩니다. 아래는 비트수에따른 연산 결과를 나타내주는 표입니다.

피연산자 나누는 연산자의 크기 나머지
AX 8bit AL AH
DX:AX 16bit AX DX
EDX:EAX 32bit EAX EDX

 

나누는 값이 8비트 상황일때 예제를 보도록 하겠습니다.

.code
mov ax, 0083h
mov bl, 2
div bl

이에 연산결과는 AL에는 몫으로 41이, AH에는 나머지로 1이 들어가게됩니다.

 

나누는 값이 16비트 상황일때 예제를 보도록 하겠습니다.

.code
mov dx, 0
mov ax, 8003h
mov cx, 100h
div cx

피연산자는 dx:ax = 0000 : 8003h 입니다. 나누는 값은 100h로 연산의 결과로, AX에는 몫으로 0080h가, DX에는 나머지로 0003h가 저장됩니다.

 

나누는 값이 32비트 상황일때 예제를 보도록 하겠습니다.

.code
mov edx, 00000008h
mov edx, 00300020h
mov ebx, 00000100h
div ebx

피연산자는 edx:eax = 00000008h:00300020h 입니다. 나누는 값은 00000100h 입니다. 연산의 결과로 eax에 몫으로 08003000h이, EDX에는 나머지로 00000020h이 저장됩니다.

 

IDIV 명령어는 부호있는 정수의 나눗셈을 실행합니다. IDIV 명령어를 사용할때는 DIV 명령어때와 다르게 ah, dx, edx를 수의 크기를 늘리는 것이 아닌 부호를 위한 비트로 사용하게됩니다. 즉, IDIV 명령어를 사용하기전에 al, ax, eax의 값을 부호를 유지시키면서 값을 확장하게됩니다. 이를 도와주는 명령어 3가지가 있습니다.

CBW AL의 부호를 AH로 확장합니다.
CWD AX의 부호를 DX로 확장합니다.
CDQ EAX의 부호를 EDX로 확장합니다.

위 명령어를 사용하여 IDIV 명령어를 사용하는 예제를 보도록 하겠습니다.

 

8비트 상황일때 예제입니다.

.code
mov al, -48
cbw
mov bl, +5
idiv bl

위 연산의 결과로 AL에는 몫으로 -9가 AH에는 나머지로 -3이 저장됩니다.

 

16비트 상황일때 예제입니다.

.code
mov ax, -5000
cwd
mov bx, +256
idiv bx

위 연산의 결과로 ax에는 몫으로 -19가 저장되며 dx에는 나머지로 -136이 저장됩니다.

 

32비트 상황일때 예제입니다.

.code
mov eax, 50000
cbq
mov ebx, -256
idiv ebx

위 연산의 결과로 eax에는 몫으로 -195가 저장되며, edx에는 나머지로 -80이 저장됩니다.

Comments