방학을 앞두고 C언어 4주차 학습 내용을 정리했습니다. 이번 주에는 함수의 다양한 유형과 특히 재귀 함수에서 발생할 수 있는 스택 오버플로우 현상을 깊이 있게 다루었습니다. 재귀 함수는 문제를 풀 때마다 틀려서 따로 학습해야 했었는데, 게을러서 매번 틀려도 넘어가다가 이제서야 제대로 알게 되었습니다…. 그럴 수도 있다
✅ 함수의 유형
※ 함수 원형 (function prototype)
반환형 함수명(매개변수1, 매개변수2);
1️⃣ 매개변수가 없는 함수
- int func(void); 혹은 int func();
- 함수 호출 시에는 괄호만 사용 ex) func()
2️⃣ 반환값이 없는 함수 (void 함수)
- void func(int a, int b);
- return만 단독으로 사용하거나, 생략 가능
- 반환값이 없기 때문에 수식 사용 불가 ex) func(a, b) + 10 ❌
3️⃣ 매개변수와 반환값이 없는 함수 (완전 void 함수)
- void func(void); 혹은 void func();
- 단순히 어떤 작업을 수행하고 결과값을 반환하지 않는 유틸리티 함수들이 이에 해당
4️⃣ 재귀호출 함수
- 자기 자신을 호출하는 함수
- 재귀 함수가 되려면 필수적으로 있어야 하는 부분
- 종료 조건을 검사하는 코드
- 재귀하며 수행할 코드
- 자기 자신을 호출하는 코드
+ C언어에서는 void를 표기하는 것을 권장
✅ 추가 숙제
실행할 코드
|
|
실행 결과
사실 코드만 봐서는 무한으로 apple을 출력해야 하는데 실행 결과를 보면 apple을 한참 출력하다가 저절로 종료됩니다.
그 이유를 생각했을 때 가장 먼저 떠오르는 키워드는 메모리였습니다.
📚 p.227
함수는 호출만으로도 일정 크기의 메모리를 사용하므로 무한 호출하면 프로그램 하나가 쓸 수 있는 메모리(해당 프로세스에 할당된 스택 메모리)를 모두 사용하게 되어 메모리 부족으로 강제 종료됩니다. 따라서 컴파일러는 컴파일 과정에서 다음과 같은 경고 메시지를 띄워 알려 줍니다.warning C4717: 'fruit': 모든 제어 경로에서 재귀적입니다. 함수로 인해 런타임 스택 오버플로가 발생합니다.
여기에서 좀 더 상세히 과정을 살펴보고 싶은 생각이 들어 fruit 함수 내부에 중단점을 두고 디버깅을 해보았는데, 레지스터 값이 계속해서 변하는 것은 확인했지만 아무리 다음 버튼을 눌러도 Stack Overflow가 발생하지 않아서 중단점을 제거하고 디버깅해 보았습니다.
0xC00000FD: Stack overflow
와 같은 예외 메시지가 팝업되고 이는 Windows 운영체제에서 스택 오버플로우가 발생했음을 나타내는 예외 코드입니다.
LIFO(Last In First Out)의 특성을 가지고 있는 Stack 메모리에 main 함수에 해당하는 메모리를 먼저 할당한 후,
다음으로 계속해서 fruit 함수에 메모리를 할당해주다가 결국 메모리를 모두 사용하고 예외가 발생합니다.
✅ 새로 알게 된 내용 (new!)
1️⃣ 인수와 매개변수의 차이
용어에 크게 신경쓰는 편이 아니라 그게 그거라고 생각했지만 엄연히 다른 용어였습니다.
- 인수 (argument) : 함수 호출 시 필요한 값
- 매개변수 (parameter) : 함수 선언 혹은 정의를 할 때 넣어주는 값
2️⃣ 메인 함수와 그 외 함수의 순서
보통 부가적인 함수를 위에 정의하고 메인 함수를 작성하는 경우를 많이 봤기 때문에 관용적인 부분이라고만 생각했는데,
메인 함수에서 다른 함수를 호출하기 전에 해당 함수의 존재와 사용법을 미리 알려주기 위해 메인 함수 위쪽에 함수 원형을 선언한다는 사실을 알게 되었습니다. 이를 통해 컴파일러는 함수 호출이 올바른 형식인지 미리 확인할 수 있습니다.