1장에서 어셈블리어로 구성된 소스 파일이 만들어지는 일련의 과정을 보았던 적이 있었다. 우리는 이 글에서 3장을 다룰 것인데, 보다 상세하게 이 부분을 다시 다룬다. 정리하고 있으면 글을 어디서 끊는게 딱 좋은지 고민이 많다.
어셈블리어
어셈블리 코드는 기계어로 전환되기 직전 상태의 소스 파일 내의 구성이다. C 언어 코드는 여러 단계의 변환을 거쳐 어셈블리 코드로 전환되고, 마지막에 기계어로 전환된다. 이 어셈블리 코드는 변수 조작을 레지스터라는 CPU 내의 정말 작고 정말 빠른 저장공간을 활용한다.
여러가지 레지스터가 있지만, 가장 중요한 범용 레지스터들을 알아두면 좋다.
%rax : 보통 함수의 반환 값을 저장하는 용도로 사용된다.
%rdi, %rsi, %rdx, %rcx, %r8, %r9 : 함수에 인자를 전달 할 때 순서대로 사용된다.
%rsp : 스택 포인터로, 스택의 끝을 가리키는 아주 중요한 레지스터이다.
%rbp : 베이스 포인터로, 스택의 기준점을 가리킨다.
간단하게 long 타입 정수 두 개를 더하는 함수를 살펴보자.
long sum(long x, long y)
{
long t = x + y;
return t;
}
이 C 코드를 컴파일하면, 아래와 같은 어셈블리 코드가 생성된다.
sum:
movq %rdi, %rax
addq %rsi, %rax
ret
sum: : 그냥 레이블이다. 함수의 이름표처럼, 어셈블리 코드에서 이 함수의 시작 위치를 나타낸다.
movq %rdi, %rax :
- movq는 Move Quadword(8바이트 데이터를 옮겨라)라는 명령어이다.
- %rdi는 함수에 전달된 첫 번째 인자(x)를 담고 있다.
- %rax는 보통 반환 값을 저장하는 데 사용되는 레지스터이다.
- 즉, 첫 번째 인자 x의 값을 t라는 변수 대신 %rax 레지스터에 복사한다.
addq %rsi, %rax :
- addq는 Add Quadword(8바이트 데이터를 더해라)라는 명령어이다.
- %rsi는 함수에 전달된 두 번째 인자(y)를 담고 있다.
- 두 번째 인자 y의 값을 %rax 레지스터에 더하고, 그 결과를 다시 %rax에 저장한다.
ret는 함수를 종료하고 호출한 곳으로 돌아가는 return과 동일하다.
이때 C 코드를 호출한 쪽에서는 약속대로 %rax 레지스터에 저장된 값을 최종 반환 값으로 사용하게 된다.
이걸 다 할 수 있어? 그러면..
어셈블리로 전환하는 주체는 전체 코드의 앞 뒤 맥락을 알고 있어야겠다.
그래야 변수 갯수들에 비해 레지스터의 갯수가 턱 없이 모자란 상황을 대비 할 수 있을 것이다...
그래!!! 변수는 많은데, 레지스터는 몇 개 없다!!! : Register Spilling
방금 논했던 앞 뒤 맥락을 알고 있어야하는 주체가 바로 컴파일러이다.
컴파일러는 이 문제를 해결하기 위해 스택이라는 메모리 공간을 아주 적극적으로 활용한다.
한 함수안에서 변수를 많이 써서 레지스터가 꽉 차면 컴파일러는 당장 쓰지 않는 변수들을 스택으로 옮겨두는 선택을 한다. 컴파일러는 레지스터에 있는 값을 스택 메모리 공간으로 올기는 어셈블리 코드를 생성하는데 이 과정을 레지스터 스필링(Register Spilling)이라고 한다.
이제 비어있는 레지스터를 가지고 다른 변수의 작업을 처리한다. 스택에 위치한 변수가 필요해지면 스택에서 레지스터로 다시 가져오는 코드를 생성한다. 이 맥락에서 스택의 위치를 추적하는데 바로 사용되는 것이 %rsp, 스택 포인터이다.
하지만 모든 함수에서 아주 흔하게 일어나는건 아니다. 레지스터 스필링을 가능한 피하려고 컴파일러는 갖가지 시도를 한다 :
- 가장 자주 사용되는 ‘핵심 변수’들을 우선적으로 레지스터에 할당한다.
- 한 변수의 수명이 끝나면 그 변수가 사용하던 레지스터를 즉시 다른 변수를 위해 재활용한다.
- 때로는 불필요한 변수를 없애버리거나, 코드 구조를 바꿔서 레지스터 사용량을 줄이기도 한다.
그럼 스택의 주된 역할은? 바로 함수를 호출하고 돌아올 때이다.
함수 호출 시점에는 어디로 돌아가야하는지에 대한 복귀 주소를 잠시 저장해둔다. 전달 해야 할 인자가 너무 많을때도 스택을 사용한다.
'컴퓨터 이론 > CS:APP' 카테고리의 다른 글
Young한 Y86-64로 구조 알아보기 [CSAPP Chap 4] (2) | 2025.08.31 |
---|---|
제어 흐름도 논하는 어셈블리 명렁어 [CSAPP Chap 3] (3) | 2025.08.27 |
부동소수점과 엔디안 논하기 [CSAPP Chap 2] (0) | 2025.08.21 |
16비트와 2의 보수는 왜 있는거임 [CSAPP Chap 2] (7) | 2025.08.21 |
운영체제의 추상화를 정돈해보니 [CSAPP Chap 1] (5) | 2025.08.15 |