본문 바로가기

카테고리 없음

3.7.5 레지스터를 이용하는 지역저장소 (예제 3.34)

 

예제를 풀면서 함수가 레지스터와 스택에 저장되는 걸 이해해보자~

long P(long x)
x in %rdi

1 P:
2 pushq %rl5
3 pushq %rl4
4 pushq %rl3
5 pushq %rl2
6 pushq %rbp
7 pushq %rbx
8 subq $24, %rsp
9 movq %rdi, %rbx
10 leaq l(%rdi), %rl5
11 leaq 2(%rdi), %rl4
12 leaq 3(%rdi), %rl3
13 leaq 4(%rdi), %rl2
14 leaq 5(%rdi), %rbp
15 leaq 6(%rdi), %rax
16 movq %rax, (%rsp)
17 leaq 7(%rdi), %rdx
18 movq %rdx, 8(%rsp)
19 movl $0, %eax
20 call Q

A. 어떤 지역 값들이 피호출자-저장 레지스터에 저장되는지 알아내시오.
B. 어떤 지역 값들이 스택에 저장되는지 답하시오.
C. 왜 이 프로그램이 모든 지역 값들을 피호출 레지스터에 저장할 수 없었는지 설명하
시오.

 

A. 어떤 지역 값들이 피호출자-저장 레지스터에 저장되는지 알아내시오.

  • 레지스터 %rbx, %rbp, %r12 ~ %r15callee-saved (피호출자-저장) 레지스터야.
  • 즉, 함수 Q를 호출하더라도 이 레지스터들은 값이 보존되어야 함.
  • 👉 여기에 저장된 지역변수는 레지스터로 유지 가능함.

line 1~7 까지는 전부 피호출자 저장 레지스터임. 기존에 있던 값을 스택에 옮겨두는 작업 먼저 실행

line 9~14 피호출자 저장 레지스터에 저장

line 15 a6

line 16 스택에 저장

line 17 a7

line 18 스택에 저장

 

line 7 : pushq %r15 < %rsp -= 8 (스택 공간 확보) + MEM[%rsp] ← %r15 (그 시점의 %r15 값을 메모리에 저장)

line 8 : subq $24, %rsp   < 스택에 24바이트 확보 , 지역변수 a6,a7을 스택에 저장할 예정. 값을 넣는게 아니고 공간만 확보

line 9 : movq %rdi, %rbx   < %rdi는 함수 P의 인자 x를 담고 있다.이 값을 지역변수로 보관하기 위해 %rbx에 복사함.

line 10 : leaq 1(%rdi), %r15  < %rdi + 1 계산 → %r15에 저장 (a1)

 

subq로 24 바이트 공간 확보한 이유는 call Q 이 8바이트를 차지하기 때문에..

→ 이렇게 해야 call 이후에도 %rsp가 16의 배수로 유지됨

 

💡 왜 앞에서 스택에 옮길때는 pushq 이고, 뒤에서는 movq ?

a6, a7은 “지역변수 값을 스택에 저장”하는 거고
pushq는 “레지스터 백업 목적”이라서 역할이 다르다!
pushq는 “현재 레지스터 값을 스택에 저장하고, rsp도 줄이는 명령”

→ 스택에 공간 만들고, 값을 올려줌

스택 포인터가 자동으로 움직여!

그런데 a6, a7 저장할 때는?

스택 포인터를 줄이지 않음
• 즉, 이미 확보된 스택 공간에 “값만 저장”하는 거야
• → 컴파일러가 미리 subq $24, %rsp로 공간 확보해뒀기 때문에!

movq %rax, (%rsp)       ; a6
movq %rdx, 8(%rsp)      ; a7

 

즉, 의도 자체가 다름!

 


B. 어떤 지역 값들이 스택에 저장되는지 답하시오.

  • 피호출자 레지스터가 모자라거나, 또는
  • 특정 구조상 레지스터에 다 넣지 못하는 지역 변수는
  • 👉 스택 메모리에 저장하게 돼.
  • 그래서 답은 a6, a7

 

C. 왜 모든 지역 값을 피호출자-저장 레지스터에 저장할 수 없었는지 설명하시오.

  • 피호출자 레지스터는 총 6개(%rbx, %rbp, %r12 ~ %r15) 뿐이니까,
  • 지역변수가 8개(a0 ~ a7)면 다 못 담음 → 남는 2개는 스택으로 내려감

 


 

✅ 1. 레지스터로 전달되는 인자 개수 규칙 (x86-64 기준)

 

x86-64에선 함수 인자 최대 6개까지만 레지스터로 전달돼:

1번째 %rdi
2번째 %rsi
3번째 %rdx
4번째 %rcx
5번째 %r8
6번째 %r9
7번째 이후 스택에 저장됨 (pushsub %rsp)

 

✅ 2. 그럼 진짜 함수 인자 6개 넘는 경우가 많을까?

🔍 실제 통계:

  • 리눅스 커널 / 오픈소스 앱 / 컴파일러 내부 함수 분석 결과 보면
  • 대부분의 함수는 인자가 3~4개 이하인 경우가 대부분이야.

 

✅ 3. 왜 6개 이상 넘기지 않는 경우가 많을까?

💡 이유는 여러 가지야:

✔ 1. 보통 함수가 “역할 단위로 잘게 나뉘기” 때문

  • 예를 들어 drawImage(x, y, width, height) ← 4개면 충분
  • “하나의 함수가 너무 많은 인자를 받는 건 나쁜 설계”라는 인식이 있음

아래 코드 예시를 보자.

void A(int a, int b, int c) { ... }
void B(int x, int y) { ... }

int main() {
    A(1, 2, 3);
    B(4, 5);
}

 

 

어셈블리 관점

mov $1, %rdi    ; A의 1번째 인자
mov $2, %rsi    ; A의 2번째 인자
mov $3, %rdx    ; A의 3번째 인자
call A

mov $4, %rdi    ; 다시 %rdi 덮어쓰기! (B의 1번째 인자)
mov $5, %rsi    ; 다시 %rsi 덮어쓰기! (B의 2번째 인자)
call B

 

✅ 그래서 중요한 사실

  • 레지스터는 “계속해서 값이 추가되는 저장소”가 아니야
  • 👉 일시적인 계산 or 인자 전달용 공간일 뿐!
  • 다음 함수가 호출되면, 기존 값은 사라지거나 덮여쓰기
  • 그래서 보존하고 싶은 값은 스택에 push해서 따로 저장해둬야 함!

 

“왜 굳이 지역변수(local variable) 라고 부르지? 그냥 변수라고 하지?”

 

이건 단순히 용어 문제가 아니라,

변수가 “어디까지 살아있는지(scope)”,

어디에 저장되는지(storage),

그리고 언제 사라지는지(lifetime) 와도 관련돼.

 

 

✅ “변수”는 크게 두 종류가 있어

그래서 함수 종료와 상관없이 저장해둘 값은 전역변수로 관리하면 된다.

 

 

  • %rdi는 함수 인자 x를 담고 있는 레지스터
  • %rbx, %r15 같은 건 지역변수 a0, a1을 저장한 레지스터

 

✅ 그럼 질문:

 

🧠 “지역변수도 레지스터에 저장될 수 있어?”

✔️ 정답: YES! 당연히 가능함!

레지스터에 저장되는 게 훨씬 빠르니까,

컴파일러는 지역변수도 레지스터에 저장하려고 시도해.

근데 조건이 있음. 

1) 레지스터 여유가 있어야 함. 

2) 변수의 주소를 직접 참조하지 않아야 함. (&a0 같은 연산이 있으면 스택에 저장해야 함.)

 


💡 인자 6개까지는 레지스터에 무조건 전달되지만,
그 레지스터들은 함수 내부에서 계속 쓸 안전한 저장공간은 아니야.
그래서 지역변수는 별도의 피호출자 저장 레지스터스택에 따로 저장돼.