함수 호출은 하드웨어에 종속적인 부분이 많다.
예를 들어 ARM 아키텍처에서는 함수의 전달 인자와 리턴 주소를 레지스터에 저장하도록 결정했다.
이처럼 각 아키텍처의 호출 규약(Calling Convention) 에 따라 함수 호출 방식이 달라진다.


스택 프레임 (Stack Frame)

함수 호출 과정에서 할당되는 메모리 블록을 스택 프레임이라고 한다.

  • 함수 호출이 완료되어 반환되면, 해당 스택 프레임 내부에 선언된 변수들에는 더 이상 접근할 수 없다.

주요 레지스터

SP (Stack Pointer)

  • 현재 스택의 최상단(top) 위치를 가리키는 레지스터.
  • 스택에 데이터를 push하거나 함수 호출 시 지역 변수 공간을 확보하면 SP 값이 이동한다.
  • 함수가 종료되면 SP를 이전 위치로 되돌려 해당 함수가 사용하던 스택 프레임을 해제한다.

PC vs SP
PC(Program Counter)가 다음에 실행할 명령어의 위치를 가리킨다면,
SP는 현재 스택의 사용 위치를 가리킨다.


FP (Frame Pointer)

SP는 스택의 현재 위치를 가리키기 때문에, 스택 프레임을 “어디서부터” 해제할지에 대한 기준점이 필요하다. 그 역할을 FP(Frame Pointer) 가 담당한다.

  • 함수가 호출되면, 호출 시점의 SP 값을 FP에 저장한다.
  • 함수 해제 시 FP에 저장된 위치까지 스택을 반환한다.

중첩 호출 문제와 해결

함수 안에서 또 다른 함수가 호출되면, FP 레지스터의 값이 덮어씌워지기 때문에 이전 함수의 프레임으로 복귀하기 어려워진다. 해결책: 함수 호출이 일어날 때마다, 현재 FP 값을 스택에 저장(백업)한 뒤 새로운 값으로 FP를 갱신한다.


PC (Program Counter)

  • 코드 영역과 관련된 레지스터로, 다음에 실행할 명령어의 주소를 가리킨다.
  • CPU의 Fetch 단계에서 코드 영역의 명령어를 가져올 때, 어디까지 실행했는지 추적하는 역할을 한다.

PC 백업의 필요성

함수1-앞부분 → (함수2 호출) → 함수1-뒷부분

함수 2가 종료된 후 함수 1의 이어지는 위치로 되돌아오려면 PC 값을 미리 저장해 두어야 한다.
이 경우에도 스택에 PC를 저장(백업) 한다.
이렇게 저장된 값을 리턴 주소(Return Address) 라고 부른다.


함수 호출 과정 예시

단계동작
① 함수 호출 직전SP는 현재 스택의 끝 위치를 가리킴
② 함수 호출 직전FP는 이전 함수의 스택 프레임 시작 위치를 가리킴
③ 함수 호출 시현재 FP 값을 SP가 가리키는 위치(스택)에 저장
현재 SP 값을 FP에 저장 (새 프레임 기준점 설정)
⑤ 함수 실행 중지역 변수 등이 쌓이며 SP가 이동
⑥ 함수 종료 시FP 값을 SP에 저장 → 현재 스택 프레임 제거
스택에 저장해 두었던 이전 FP 값을 복구

함수 호출 vs 프로시저 호출

구분설명
함수 호출입력에 대한 반환값(출력)이 존재하는 호출
프로시저 호출반환값 없이 특정 동작만 수행하도록 모듈화한 호출

함수 인자(Arguments)는 어디에 저장되는가?

CPU 아키텍처 및 호출 규약(Calling Convention)에 따라 다르지만, 크게 두 가지 방식이 있다.

  • 스택 저장 방식: 지역 변수와 마찬가지로 스택에 할당 (전통적인 방식)
  • 레지스터 저장 방식: 성능 향상을 위해 레지스터에 저장 (ARM, x86-64 등 현대 아키텍처에서 주로 사용)

기본 질문 5개

1. 스택 프레임이란 무엇인가요?

함수 호출 시 해당 함수가 실행되는 동안 필요한 정보를 저장하기 위해 스택에 만들어지는 영역입니다.
보통 매개변수, 반환 주소, 이전 FP, 지역 변수 등이 포함됩니다.

꼬리 질문:

  • 스택 프레임은 언제 생성되고 언제 제거되나요?
  • 지역 변수는 왜 스택에 저장되나요?

2. SP와 FP의 차이는 무엇인가요?

SP는 현재 스택의 끝 위치를 가리키는 레지스터이고,
FP는 현재 함수의 스택 프레임 기준점을 가리키는 레지스터입니다.

SP는 지역 변수 push/pop 등에 따라 계속 변할 수 있지만,
FP는 함수 실행 중 기준점 역할을 하기 때문에 상대적으로 고정됩니다.

꼬리 질문:

  • FP가 있으면 어떤 점이 편한가요?
  • 최적화 옵션에서 FP를 생략할 수 있는 이유는 무엇인가요?

3. 함수 호출 시 스택에는 어떤 정보들이 저장되나요?

일반적으로 반환 주소, 이전 FP, 매개변수, 지역 변수 등이 저장됩니다.
아키텍처와 호출 규약에 따라 일부 매개변수는 레지스터로 전달될 수도 있습니다.

꼬리 질문:

  • 반환 주소는 왜 필요한가요?
  • 이전 FP는 왜 저장하나요?

4. 함수가 종료될 때 스택 프레임은 어떻게 제거되나요?

함수 종료 시 SP를 FP 위치로 되돌려 지역 변수 영역을 제거하고,
스택에 저장해 둔 이전 FP를 복구한 뒤, 반환 주소를 이용해 호출자 코드로 돌아갑니다.

꼬리 질문:

  • SP = FP를 하면 어떤 효과가 있나요?
  • ret 명령은 어떤 역할을 하나요?

5. 호출 규약이란 무엇인가요?

함수를 호출할 때 매개변수를 어떻게 전달할지, 반환값을 어디에 둘지, 스택 정리는 누가 할지 등을 정한 약속입니다.
대표적으로 cdecl, stdcall, fastcall 등이 있습니다.

꼬리 질문:

  • 호출 규약이 다르면 어떤 문제가 생길 수 있나요?
  • C++ 멤버 함수 호출에는 어떤 특징이 있나요?

고난이도 질문 5개

1. 호출 규약에서 Caller와 Callee의 책임 차이를 설명해보세요.

핵심은 스택 정리와 레지스터 보존 책임이 누구에게 있느냐입니다.
예를 들어 cdecl은 호출자가 스택을 정리하고, stdcall은 피호출자가 스택을 정리합니다.
또한 호출 규약에 따라 caller-saved 레지스터와 callee-saved 레지스터가 구분됩니다.

꼬리 질문:

  • caller-saved와 callee-saved 레지스터의 차이는 무엇인가요?
  • 함수 호출 전후로 레지스터 값을 보존해야 하는 이유는 무엇인가요?

2. cdeclstdcall의 차이를 설명하고, 가변 인자 함수에서 cdecl이 사용되는 이유를 말해보세요.

cdecl은 호출자가 스택을 정리하고, stdcall은 피호출자가 스택을 정리합니다.
가변 인자 함수는 피호출자가 인자의 개수를 정확히 알기 어렵기 때문에, 호출자가 스택을 정리하는 cdecl 방식이 적합합니다.

꼬리 질문:

  • printf 같은 함수가 cdecl을 사용하는 이유는 무엇인가요?
  • 스택 정리 주체가 다르면 어셈블리 코드에서 어떤 차이가 나나요?

3. FP를 생략하는 최적화가 가능한 이유와 그 단점을 설명해보세요.

컴파일러는 FP를 일반 레지스터처럼 활용하기 위해 프레임 포인터를 생략할 수 있습니다.
이 경우 지역 변수나 매개변수 접근은 SP 기준 오프셋으로 처리됩니다.

장점은 사용 가능한 레지스터가 늘어나고 코드가 최적화될 수 있다는 점입니다.
단점은 디버깅이나 스택 트레이스 분석이 어려워질 수 있다는 점입니다.

꼬리 질문:

  • FP 없이도 지역 변수 위치를 찾을 수 있는 이유는 무엇인가요?
  • 디버거는 스택 프레임을 어떻게 추적하나요?

4. 재귀 함수 호출 시 스택 프레임은 어떻게 구성되며, 스택 오버플로우는 왜 발생하나요?

재귀 함수는 호출될 때마다 새로운 스택 프레임을 생성합니다.
같은 함수라도 호출 횟수만큼 독립적인 지역 변수, 반환 주소, 이전 FP 정보가 쌓입니다.

재귀 깊이가 너무 깊어지면 스택 영역을 초과하게 되고, 이때 스택 오버플로우가 발생합니다.

꼬리 질문:

  • 같은 함수의 지역 변수가 호출마다 독립적인 이유는 무엇인가요?
  • 1꼬리 재귀 최적화가 스택 사용량을 줄이는 원리는 무엇인가요?

5. 함수 호출 중 스택 정렬이 필요한 이유를 설명해보세요.

2 일부 아키텍처나 SIMD 명령어는 특정 바이트 단위 정렬된 메모리 접근을 요구하거나, 정렬된 접근에서 더 효율적으로 동작합니다.
따라서 호출 규약은 함수 호출 시 스택을 일정 단위로 정렬하도록 요구할 수 있습니다.

예를 들어 x64 환경에서는 함수 호출 전 스택 정렬 규칙이 중요하며, 이를 어기면 성능 저하나 크래시가 발생할 수 있습니다.

꼬리 질문:

  • 스택 정렬이 깨지면 어떤 문제가 생길 수 있나요?
  • SIMD 명령어와 스택 정렬은 어떤 관련이 있나요?

Footnotes

  1. 꼬리 재귀 최적화는 재귀 호출을 하더라도 새로운 스택 프레임을 계속 쌓지 않도록, 컴파일러가 재귀를 반복문처럼 바꿔주는 최적화 핵심 조건은: 재귀 호출이 함수의 “마지막 동작”이어야 한다

  2. 스택 정렬은 말 그대로 SP가 특정 바이트 단위에 맞춰진 주소를 가리키도록 유지하는 것. 예를 들어 “스택을 16바이트 정렬한다”는 말은 SP 주소가 16의 배수가 되도록 맞춘다는 뜻