1. 매개변수가 있는 함수
- main 스택 프레임을 생성한다. 여기에 지역변수 a, b, result 값이 저장된다.
- result = sum (a, b); 를 실행하기 전 sum 함수 호출 준비를 한다. cpu는 현재 다음에 실행해야 할 명령어의 주소(sum 함수가 끝난 후 돌아와야 할 result에 값을 대입하는 명령의 주소)를 복귀주소로 스택에 저장한다.
- sum 함수로 제어가 이동되어 main 프레임 위에 sum 함수를 위한 새로운 스택 프레임이 쌓인다. 전달된 인자(10, 20)은 sum 함수의 매개변수 x와 y에 할당된다. 함수 본체가 실행된 후 x+y의 결과 30이 계산된다.
- return x + y; 명령이 실행되면 계산된 결과값 30을 반환값으로 레지스터에 저장한다. sum 함수는 스택에 저장했던 복귀주소를 프로그램 카운터 레지스터에 로드한다. sum 함수의 스택프레임은 소멸되어 스택에서 제거된다.
- 프로그램 제어가 복귀 주소로 돌아와 main 함수가 중단되었던 지점부터 다시 실행된다.


1-1. 매개변수가 있는 함수 - char 형과 int 형

2. 매개 변수가 없는 함수: int get_num(void) 또는 int get_num()
호출스택의 과정 → 함수 내부에 있을때에는 호출스택에 쌓여있다가, 함수를 빠져나오면 스택에서 빠져나오는 것을 알 수 있음
main 스택 → get_num 스택 쌓이고, 빠지고 → myPrint 스택 쌓이고, 빠









3. 재귀함수 - 스택 메모리는 크기 제한이 있어(1MB ~ 8MB 라고 함) 이 공간이 완전히 차면 프로그램은 스택 프레임을 쌓을 수 없게 되고 이때 "스택 오버플로우" 오류가 발생한다. 이 현상은 운영체제 수준의 오류인 세그먼트 폴트나 프로그램 강제 종료로 이어진다. 재귀는 메모리 소모가 크고 예측이 어려워 성능과 안정성을 위해 반복문 사용이 권장되기도 한다.



코드 구조
int main() {
recursive(0); // n=0부터 시작
return 0;
}
int recursive(int n) {
printf("number: %d\n", n);
if (n == 10) {
return 0; // 기저 조건: n이 10이 되면 반환 (재귀 종료)
}
// 재귀 호출
recursive(n + 1);
// 10 올라오고 스택이 빠지기 때문에 9, 8, 7, ... 이렇게 빠짐
}
호출 스택 (Call Stack)의 LIFO 동작 시뮬레이션
| 단계 | 실행 내용 (함수 호출) | 스택 변화 (LIFO) | 설명 |
| 1 | main 호출 | [main] | main 스택 프레임이 가장 먼저 쌓임. |
| 2 | recursive(0) 호출 | [main] <- [n=0] | n=0 프레임이 쌓이고, "number: 0" 출력. |
| 3 | recursive(1) 호출 | [main] <- [n=0] <- [n=1] | n=1 프레임이 쌓이고, "number: 1" 출력. |
| ... | ... | ... | 이 과정이 n=10이 될 때까지 반복됨. |
| 12 | recursive(10) 호출 | ... <- [n=9] <- [n=10] | n=10 프레임이 쌓이고, "number: 10" 출력. |
| 13 | n == 10 (return 0) | [n=10] 프레임이 제거됨 (Pop). | 가장 나중에 들어온 n=10이 가장 먼저 나감. (LIFO) |
| 14 | 복귀 (제어권 n=9로) | ... <- [n=9] | n=9 프레임이 실행을 재개. (하지만 더 이상 코드가 없어 즉시 종료) |
| 15 | n=9 종료 | [n=9] 프레임이 제거됨 (Pop). | n=9가 n=10 다음에 나감. |
| ... | ... | ... | n=8, 7, 6, ... 순서로 순차적으로 스택에서 빠져나감. |
| 23 | n=0 종료 | [main] | n=0 프레임이 가장 마지막에 나감. |
| 24 | main 종료 | [ ] | main 프레임이 제거되고 프로그램 종료. |
재귀 함수가 헷갈려서 정리했는데, 틀린게 있을수도 있다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int fruit(int n);
int main() {
fruit(1);
return 0;
}
int fruit(int count) {
printf("apple: %d\n", count);
if (count == 3)
return 0; // 함수를 끝낸다
fruit(count + 1);
printf("jam\n");
}
1. 스택 쌓기 (재귀 호출)
가장 깊은 호출이 발생할 때까지 스택에 쌓이는 과정
호출 count 값 출력 다음 행동| 호출 | count 값 | 출력 | 다음 행동 |
| fruit(1) | 1 | apple: 1 | fruit(2) 호출 (스택에 대기) |
| fruit(2) | 2 | apple: 2 | fruit(3) 호출 (스택에 대기) |
| fruit(3) | 3 | apple: 3 | if (count == 3) 조건 만족, return 0; 실행. => 제어권이 fruit(2)로 복귀. |
2. 스택 풀기 (복귀 및 나머지 코드 실행)
fruit(3)이 종료된 후, 제어권이 이전 함수로 돌아가 나머지 코드를 순차적으로 실행
복귀 재개 함수 fruit 내부 재개 지점 출력 설명| 복귀 | 재개 함수 | fruit 내부 재개 지점 | 출력 | 설명 |
| 1차 복귀 | fruit(2) | fruit(3) 호출 직후 | jam | fruit(3) 종료 후, printf("jam\\n"); 실행. fruit(2) 종료. |
| 2차 복귀 | fruit(1) | fruit(2) 호출 직후 | jam | fruit(2) 종료 후, printf("jam\\n"); 실행. fruit(1) 종료. |
4. 배열 - 배열의 이름은 그 배열의 첫번째 요소의 메모리 주소(번지)를 나타낸다. 따라서 배열 이름은 상수형 포인터(const pointer) 처럼 동작한다.
- 배열의 이름(arr) 자체는 arr[0]의 주소 즉 &arr[0]과 동일한 값을 가진다
- 배열 이름이 포인터처럼 주소값을 가지더라도 이 주소값 자체를 변경할 수는 없다. 즉 arr = arr + 1; 과 같은 코드는 허용되지 않는다 (일반적인 변수형 포인터와의 가장 큰 차이라고는 한다.)
- 배열 요소를 포인터로 접근하는 방법 - 배열 접근 방식(arr[i]), 포인터 접근 방식(*(arr + i))은 컴파일러 수준에서 동일하게 처리된다



5. 그 외 함수를 이용한 코딩
사칙연산 함수

평균을 구하는 함수

'c' 카테고리의 다른 글
| 포인터 (0) | 2025.11.06 |
|---|---|
| 배열 (0) | 2025.11.05 |
| 반복문 (while, for) (0) | 2025.11.03 |
| c 언어 (0) | 2025.11.02 |
| C 언어 - 연산자, 조건문(1) (0) | 2025.10.30 |