int main / void main 차이 (리눅스기준)
뭐 별건 없다. 사실 void로 선언해도 보통 잘 되잖아? 한번 알아보도록 하자.
int main에서 리턴을 한다는 것을 생각을 해보자. 뭔가 정수값을 하나 리턴하지?
int main() {
return 0;
}
초간단한 main 함수다. 왜 메인에서 되도않는 값을 하나 리턴하는걸까? 이게 어디 쓰이는지 한번 볼게.
main이 어디서 호출이 되는지 봐야하거든?
여기서부터는 컴파일러마다 쪼금씩 다른데, 기본적으로 숨겨진 라이브러리들이랑 링크를 하게 된다.
main함수를 호출해주는건 C 표준 라이브러리고 보통 libc라고 많이 부름. 윈도에선 msvcrt라고 한다. vc runtime. ㅇ.
이 libc에는 main을 호출하는 __libc_start_main이라는 함수가 있다.
안에서 뭐 하는지는 대충 넘기고, main 호출 관련된 구분은 이래.
/* Nothing fancy, just call the function. */
result = main (argc, argv, __environ MAIN_AUXVEC_PARAM);
...
exit (result);
ㅇㅇ. 이 말은 뭐다? main에서 exit를 호출해도 main에서 무언가를 리턴하는거랑 똑같다는거야.
main의 리턴값은 exit로 가는거지.
근데 exit 코드는 어디 쓰이냐? 어디 쓰이냐면,
뭐 딱히 없다. 그냥 쓰고싶은대로 쓸 수 있는데, 너네가 일단 저걸 ./program 이라는 실행 파일로 만들었다고 해봐.
$ ./program
그럼 이제 exit code가 지정되었겠지? 이 지정된 값을 우리가 얻어올 수가 있어.
$ echo $?
0
자, 근데 이러면 좀 별로 쓸모없어보이는거같지? 근데 exit code가 0인거는 약간 특별한 의미야.
0이면 프로그램이 성공했다, 0이 아니면 프로그램에 뭔가 에러가 있었다라고 여기는 곳이 많거든.
그래서 아래와 같은 문법이 있다. exit(0);이라고 해보자. return 0이던 뭐던.
$ ./program && sleep 5
! 이러면 5초 기다려진다.
이번엔 exit(1); 을 해보자.
$ ./program || sleep 5
! 이래도 된다. 이번엔 exit(2);를 해보자.
$ ./program || sleep 5
똑같겠지 뭐 그냥 0이 아니면 다 똑같다는 말을 하고 싶었음
저게 왜 저렇게 작동하는지 궁금하다고? 쉘 소스코드 까보면 나올거임
근데 &&랑 ||하면 뭐가 생각나겠냐? C언어에도 and랑 or할때 저거 쓰지? 쉘 스크립트 해본사람은 대충 감이 올텐데,
쉘 스크립트에서도 if문에 넣을때 쓰이는 참, 거짓값이 exit code다.
뭐 하튼 그래서 저게 리턴값이 저렇게 쓰일수 있다 라는것은 알겠고, 그럼 void main()은 int랑 뭔 차이가 있냐? 하면
void로 선언했을 경우에는 리턴 값을 신경을 안써. 그래서 사실 어떤 값이 리턴 값으로 올지는 모르거든. 근데 main의 리턴값을 어떻게 libc_start_main에서 받을까?
답은, C언어에서는 뭐 딱히 함수 타입 신경 안써도 잘만 호출되거든.
libc_start_main에 들어가는 인자는 int를 리턴하는 함수 포인터값인거다. 아래는 함수 선언이야.
STATIC int LIBC_START_MAIN (int (*main) (int, char **, char **
MAIN_AUXVEC_DECL),
... (생략) (존나많음)
근데 int가 아니네? 딱히 상관 없다. 함수 포인터는 서로 호환이 되기도 하고, 사실 C언어 링커는 함수 타입같은거 신경 안써. C++ 링커는 신경을 쓰는데, 걔는 사실 애초에 함수 이름이 인자 타입마다 내부적으로 다 다르게 되있다. C++ mangling이라고 검색해보면 나와.
뭐, 그러니까, void인지 int인지 사실 링커에서는 신경을 안쓴다는거야. 그럼 리턴 값은 어디서 나올까?
자, 이제 어셈블리어로 쪼금만 들어가보자. 맨 위의 초간단한 main을 한번 다시 불러와볼게.
int main() { return 0; }
ㅇㅇ. 어셈블리어로 어떨까? (보통 함수 앞뒤에 뭔가 붙기는 하는데 딱히 이걸 설명하는데는 중요하지 않을거같아서 뺌)
main:
mov eax, 0
ret
ㅇㅇ
존나 쉽지?
eax라는 "레지스터"에 0을 넣는다. 레지스터는 CPU에 있는 임시 변수같은거야.
그리고 보통 C언어에서 함수 호출을 할 때는 eax라는 "레지스터"에 리턴 값을 넣지. 실수형 리턴값은 xmm0에 넣고.. 어쩌구 저쩌구
뭐 하여튼 그렇다고. 근데 void면 어떨까?
void main() {}
main:
ret
ㅇㅇ. eax가 지정이 안된다. 뭐 컴파일러 종류나 버전마다 약간 다르긴 할거야.
CPU를 대충 순차적으로 실행하는 기계라고 생각을 해보면 eax가 지정이 안되어있다면 그 전에 지정된 eax가 리턴되겠지?
간단하게 말하면 신경을 안쓴다는거야.
그럼 뭐 대충 이런 경우는 어떨까?
int f() { return 1; }
void main() {
f();
}
어셈블리어는 어떨까?
f:
mov eax, 1
retn
main:
call f
retn
결과부터 말하면 1이 리턴된다. 엌ㅋㅋ 뭐 그렇다고. 안녕!