스택(Stack)은 컴퓨터 자료 구조의 하나로
접시가 쌓여있는 모양을 스택이라고 하는데
쌓을 때는 밑에서 하나씩 올라가지만
꺼낼 때는 위에서 하나씩 가져가는
그런 형태의 자료구조입니다.
이 직관적인 자료형은 이미
모두가 사용하고 있습니다.
웹브라우저의 뒤로가기 기능은
흔한 스택구조의 예시입니다.
인터넷을 서핑하면 링크를 클릭하여
계속 새로운 페이지로 들어갑니다.
예를 들어 사용자가 다음과 같이
웹서핑을 하고 있습니다.
네이버 -> 블로그 -> 커뮤니티 -> 웹사이트
stack 은 push 와 pop 으로만 자료를 이동합니다.
push 네이버
push 블로그
push 커뮤니티
현재 위치인 웹사이트에서
바로 전으로 돌아가고 싶다면
pop 커뮤니티
커뮤니티로 돌아갑니다.
pop 블로그
pop 네이버
까지 하면 네이버가 시작점이니까
돌아갈 곳이 없습니다.
물론 브라우저에는 앞으로 가는 기능도
있고 검색기능도 있지만 기본은 스택의
방식으로 작동합니다. 스택이 직관적인건
사람의 사고방식과 관련이 있는데요.
인간은 망각의 동물이라서 시간이 지날 수록
기억이 계속 소실됩니다. 저녁이 되면
아침에 뭘 먹었는지 기억이 잘 안납니다.
점심에 먹은 것은 아침밥보다는 기억이 더 잘 납니다.
사람은 바로 직전의 일을 그 전보다
잘 기억하기 때문에 오래전의 기억을
꺼내기 위해서는 '기억을 더듬어 찾는다'고 합니다.
스택 구조로 기억을 거슬러 올라갑니다.
*이제 컴퓨터로 돌아와서...
CPU에는 스택 포인터를 위한 레지스터가 있습니다. (rsp)
rsp 레지스터는 스택의 top(가장 최근의 값) 주소가
저장되어 있는데 push와 pop 명령어로 조작합니다.
사용법은 간단합니다.
push 값
pop 레지스터
입니다.
pop은 레지스터에 값을 꺼내옵니다.
그냥 top에 있는 값을 확인만 하려면
peek을 합니다. (peek - 엿보다)
어셈블리어는 원초적인 코드라서
mov 레지스터, [rsp]
(rsp는 주소이므로 [ ] 을 씌워서 값을 가져옴)
이런 식으로 peek 할 수 있습니다.
다음 코드는 스택에 push, pop, peek 하는 예제입니다.
section .data
text1: db 10, "[stack : push, pop, peek]", 10
text1_L: equ $-text1
SYS_write equ 1
FD_write equ 1
EXIT_SUCESS equ 0
SYS_exit equ 60
digit db 0, 10
section .text
global _start
_start:
call _printText1
;stack push, pop, peek
push 7
push 5
push 9
pop rax
call _printRAXDigit
pop rax
call _printRAXDigit
mov rax, [rsp] ;peek
call _printRAXDigit
pop rax
call _printRAXDigit
;terminate program
_last:
mov rax, SYS_exit
mov rdi, EXIT_SUCESS
syscall
_printText1:
mov rax, SYS_write ;syscall id 1 ->sys_write
mov rdi, FD_write ;file descriptor stdout
mov rsi, text1 ;hello world text
mov rdx, text1_L ;byte length
syscall
ret ;return to where it called
_printRAXDigit:
add rax, 48
mov [digit], al ;write a byte
mov rax, SYS_write
mov rdi, FD_write
mov rsi, digit
mov rdx, 2
syscall
ret
[실행결과]
[stack : push, pop, peek]
9
5
7
7
push 한 값을 pop으로 찾아온다는
것은 어렵지 않을 겁니다.
peek (mov rax, [rsp])를 해도 아직
스택에 값은 남아있으니 스택을
비우기 위해서는 pop을 끝까지 해줍니다.
stack은 하나씩 값을 넣고 빼기 때문에
push와 pop은 포인터 연산입니다.
0부터 9까지 출력하기 위한 서브루틴입니다.
숫자를 출력하는 것은 Hello World 에서
문자열을 출력하는 것과 또 다릅니다.
간단하게 사용할 수 있는 서브루틴입니다.
section .data
digit db 0, 10
_printRAXDigit:
add rax, 48
mov [digit], al ;write a byte
mov rax, SYS_write
mov rdi, FD_write
mov rsi, digit
mov rdx, 2
syscall
ret
48의 아스키 코드는 0 입니다.
즉 48이 0, 49가 1, 50이 2...
해서 0부터 9까지 출력할 수 있습니다.
mov [digit], al 은 digit에 al
즉 1바이트를 씁니다. (48)
그 다음은 시스템콜로 숫자에
대응하는 아스키코드를 출력합니다.
두자리수 이상 출력하려면 별도로
서브루틴을 만들어야 하는데
그건 복잡하니까 간단한 코드로
작성해 봤습니다.
x86 64 CPU Register 기초 - NASM x86_64 어셈블리어 3