int main / void main 차이 (리눅스기준)

Posted by 나에요임마
2017. 7. 18. 00:07 Program/C


뭐 별건 없다. 사실 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이 리턴된다. 엌ㅋㅋ 뭐 그렇다고. 안녕!