네, 맞습니다. 오늘도 지식의 충돌이 발생했습니다. 일단 코드부터 보겠습니다.
#include <iostream>
using namespace std;
//함수의 선언부 : 함수의 선언만 합니다.
int Add(int num1, int num2);
int main()
{
int num1 = 5, num2 = 10;
//선언한 함수를 호출
cout << Add(num1, num2) << endl;
return 0;
}
//함수의 정의부 : 함수의 선언부에서 선언한 함수의 몸체를 구현
int Add(int num1, int num2)
{
return num1+num2;
}
음 보기가 좀 어렵네요... 일단 어떤 문제인지 설명해드리겠습니다. 제 머리에서는 코드는 위에서 아래로 실행된다고 배웠습니다. 지금까지도 그걸 가정하고 코딩을 해왔습니다. 근데 몸체가 없는 함수를 main 함수에서 호출을 했는데 정상적으로 15가 출력되었다.
음... 왜? 음.... 흠.... 그래서 조금 찾아보다보니 해답을 알게되었습니다.
일단 함수의 선언 부
int Add(int num1, int num2);
이 코드를 빼고 코드를 실행하면 컴파일 오류가 난다.
'Add' was not declared in this scope (함수가 선언되어 있지 않았습니다.)
왜 이런 오류가 뜬걸까요? 이유는 간단합니다!
우리가 컴파일을 진행시 main 함수에서 Add 함수를 호출하는 구문을 만나면, 컴파일러는 함수의 호출문에 문법적인 검사를 시작합니다. 함수의 호출이 올바른지 확인하기 위해서는 함수의 시그니처(매개변수와 반환 타입)를 알아야하는데 함수의 시그니처를 확인할 선언문이 없기 때문입니다.
자 그럼 원래의 코드는 왜 실행되는지 알아보겠습니다. 너무 깊게는 들어가지 않고 문제의 해결에 필요한 정도만 알아보겠습니다!
일단 위 코드를 컴파일을 시키면 각 소스파일들을 독립적으로 컴파일러가 위에서 아래로 코드를 해석하며 문법적 오류가 없는지 확인을 합니다. 컴파일러가 함수의 선언부를 만나면 함수가 정의되어있지 않더라도 함수의 선언만으로 함수의 호출을 만났을때 함수의 시그니처를 알 수 있기 때문에 그대로 컴파일을 진행합니다.
자 컴파일이 끝나면 링커가 링킹 작업을 시작합니다. 링킹작업은 링커가 컴파일된 객체 파일들을 결합하여 하나의 실행파일을 만듭니다. 이 과정에서 떨어져있는 함수의 호출과 함수의 정의를 간의 주소를 연결합니다. 굉장히 중요합니다. 잘기억해주세요.
자 링킹작업도 끝났으니 이제 실행파일이 실행됩니다. 실행단계에서는 CPU가 main 함수부터 실행하기 시작합니다. 이제 main 함수를 실행하는 도중 Add 함수의 호출을 만나면 main 함수의 아래에 있는 함수의 정의문으로 이동해 코드를 실행시키고 return합니다. 이 과정에서 main 함수의 위에 있는 함수의 선언문에는 일절 접근하지 않습니다.
위에 함수의 선언만 하는건 내가 함수의 선언과 정의를 아래에 하고싶은데 그 상태에서 내가 컴파일을 진행하면 컴파일러가 함수의 호출 시점에서 함수의 시그니처가 올바른지 알 수 가 없어 컴파일 오류가 나기 때문입니다.
컴파일 단계에서 함수의 선언만 있다면 즉 함수의 시그니처만 있다면 함수 호출시점에서 함수의 시그니처를 검사할 수 있고 컴파일러는 함수의 선언만 있을시 함수의 정의는 어딨는지 관계없이 컴파일을 진행한됩니다.
그 이유는 컴파일이 끝난 뒤 링커가 링크 작업을 통해 모든파일을 결합하여 함수의 호출과 정의를 맵핑하기 떄문입니다.
- 코드는 컴파일 과정에서만 위에서 아래로 흐르며 문법적 오류가 없나 해석합니다.
- 함수의 선언만 있어도 컴파일 과정에서 함수의 호출문을 만나도 함수의 시그니처를 확인하는데 문제가 없기에 컴파일이 계속 진행됩니다.
- 링커를 통해 링킹작업을 마친 실행파일은 함수의 호출문과 정의문을 연결하기 때문에 이 코드는 문제없이 실해됩니다.
어쩌다보니 컴파일 과정에 대해 공부를 하게 되었네요... 근데 솔직히 재밌어서 좋았습니다.