IT/C and C++

Main 함수

KSI 2006. 8. 13. 08:46
main도 일종의 함수이므로 인수를 가질 수 있고 리턴값도 가질 수 있다. main은 프로그램 실행 직후에 자동으로 호출된다는 점에 있어서 일반 함수와는 다른 특별한 면이 있으며 프로그램의 시작점이기 때문에 이름은 고정되어 있지만 함수의 원형은 고정적이지 않다. main 함수의 원형은 다소 복잡한데 다음과 같은 조합이 가능하다.

void(또는 int) main(int argc,char *argv[],char *env[]);

리턴값은 int형이거나 void형 중 하나를 선택할 수 있으며 세 개의 인수를 가지는데 인수는 뒤쪽부터 차례대로 생략 가능하다. 그래서 main 함수의 가능한 원형은 다음 여덟가지나 된다.

① void main(void);

② void main(int argc);

③ void main(int argc,char *argv[]);

④ void main(int argc,char *argv[],char *env[]);

⑤ int main(void);

⑥ int main(int argc);

⑦ int main(int argc,char *argv[]);

⑧ int main(int argc,char *argv[],char *env[]);

이 중 자주 사용되는 형식은 ①번과 ③번인데 이 책의 예제들은 주로 제일 간단한 ①번 형식을 사용하고 있다. main 함수의 가장 완벽한 원형은 ⑧번인데 이 원형에서 리턴값과 인수들의 의미에 대해 알아보자.

리턴값

리턴값은 없거나 있다면 정수형이어야 한다. C++ 표준 문서(43p)에는 정확하게 It shall have a return type of type int, but otherwise its type is implementation defined 이렇게 되어 있다. main 함수의 인수는 int형의 타입을 가지는 것이 좋지만 구현 방식에 따라 다른 타입을 가지는 것도 가능하다. 즉 C++ 스팩은 int를 권장하기는 하지만 강요하지는 않는다. 비주얼 C++은 int, void 모두 가능한데 과거부터 이렇게 써 왔기 때문에 호환성을 더 중요시한다는 입장이다. gcc는 main 함수의 리턴 타입으로 int만 인정하는데 스팩의 권고를 그대로 따른 것이다.

main 함수가 리턴하는 값을 탈출 코드(Exit Code)라고 하는데 프로그램이 실행을 마치고 운영체제로 복귀할 때 리턴되는 값이다. 함수가 작업 결과를 호출원으로 돌려주듯이 응용 프로그램도 작업결과를 리턴할 수 있다. main 함수가 프로그램 그 자체이므로 main 함수의 리턴값이 곧 프로그램의 리턴값이 된다.

탈출 코드는 보통 사용되지 않고 무시되는데 이 프로그램을 호출한 프로그램(보통 쉘)이 꼭 필요할 경우 탈출 코드를 사용하기도 한다. 예를 들어 도스의 배치 파일(*.bat) 내에서 응용 프로그램을 실행했을 때 이 프로그램의 실행 결과를 ERRORLEVEL이라는 환경 변수로 참조할 수 있다. 32비트 환경에서는 탈출 코드외에도 응용 프로그램간의 통신을 위한 장치가 많이 준비되어 있어 요즘은 main의 리턴값을 잘 사용하지 않는다.

당연한 얘기겠지만 main 함수가 int형을 리턴할 때, 즉 원형을 int main()으로 했을 때는 main의 끝에 반드시 return문이 있어야 하며 그렇지 않을 경우 리턴값이 없다는 경고가 발생한다. 다른 함수들은 값을 리턴하지 않을 경우 에러로 처리되지만 main 함수만큼은 경고로 처리한다는 면에서 컴파일러가 main을 조금 특수하게 취급하는 것을 알 수 있다.
int main()
{
}
int func()
{
}
경고로 처리됨에러로 처리됨

C++ 표준에는 main이 값을 리턴하지 않을 경우를 인정하고 있으므로 main은 설사 리턴 타입이 int이더라도 return문을 생략할 수 있다. 비주얼 C++ 7.0이나 gcc로 테스트해 보면 과연 그렇다는 것을 확인할 수 있을 것이다. 그러나 비주얼 C++ 6.0은 표준 이전의 컴파일러라 경고를 출력하며 그래서 이 귀찮은 경고를 보지 않기 위해 이 교제의 모든 예제들은 void main() 형식을 사용한다.

argc

운영체제가 이 프로그램을 실행했을 때 전달되는 인수의 개수이다. 함수를 호출할 때 인수를 전달하듯이 운영체제가 프로그램을 호출할 때도 인수를 전달할 수 있다. 만약 파일끼리 복사하는 boksa.exe라는 프로그램을 작성했다고 하자. 이 프로그램은 복사할 원본 파일과 복사 대상 파일의 이름을 다음과 같이 인수로 전달받을 것이다.

boksa file1.txt file2.txt

이때 실행 파일의 이름인 boksa뒤의 file1.txt와 file2.txt가 프로그램의 인수이다. 이 인수들은 운영체제가 프로그램으로 전달하는 작업거리, 즉 어떤 일을 하라는 정보들이다. main 함수가 프로그램의 시작점이기 때문에 프로그램의 인수가 main 함수의 인수로 전달된다.

argc 인수는 main 함수로 전달된 인수의 개수이다. 첫 번째 인수는 실행 파일명으로 고정되어 있으므로 argc는 항상 1보다 크다. boksa a b 식으로 호출할 경우 argc는 3이 된다. 이 값은 인수를 필요로 하는 프로그램에서 인수가 제대로 입력되었는지를 검사할 때 사용한다. 인수가 없으면 실행할 수 없는 경우 이 값을 조사해서 인수가 제대로 전달되었는지 확인한다.

만약 필요한 인수가 없다거나 또는 남는다면 에러 메시지를 출력하고 프로그램을 끝내거나 아니면 디폴트를 취해야 한다. 예를 들어 boksa.exe는 복사 원본과 복사 대상 파일이 반드시 전달되어야 하므로 argc가 3 미만일 경우 "복사할 파일을 입력하세요"라는 에러 메시지를 출력하면 될 것이다.

argv

프로그램으로 전달된 실제 인수값이며 이 값을 읽으면 운영체제로부터 어떤 인수가 전달되었는지 알 수 있다. 운영체제가 프로그램을 실행할 때는 항상 문자열 형태의 쉘 명령을 입력하기 때문에 인수의 타입은 항상 문자열일 수밖에 없다. file1.txt라고 하든 1234라고 하든 명령행에서는 정수니 실수니 하는 것들이 없으므로 입력된 액면 그대로 문자열 인수가 전달된다. 만약 전달된 인수가 정수라면 이란 문자열로 받아서 atoi 등의 변환 함수로 정수로 바꿔 사용해야 한다.

이런 문자열 인수가 한꺼번에 여러 개 전달될 수 있다. boksa file1.txt file2.txt의 경우 boksa, file1.txt, file2.txt 세 개의 문자열이 인수로 전달된다. 그래서 argv는 문자형 포인터를 가리키는 포인터여야 한다. 간단히 말하자면 argv는 문자형 이중 포인터이고 좀 더 현실적으로 얘기하자면 문자열 배열이다. 원형에는 char *argv[]로 되어 있는데 이 표현은 char **argv와 같은 것이다. main 함수의 인수를 함수 부분에서 설명하지 못하고 여기까지 미룬 이유가 바로 이 함수가 이중 포인터를 사용하기 때문이다.

argv[0]는 항상 프로그램의 완전 경로가 전달된다. C:\prg 디렉토리에 있는 boksa.exe를 실행했다면 argv[0]는 "C:\prg\boksa.exe"가 전달된다. 이 값은 우리가 원하는 인수, 즉 프로그램의 작업거리라고는 할 수 없어 잘 사용되지 않는다. 프로그램으로 전달된 첫 번째 인수는 argv[1]로 전달되며 두 번째 인수 이후부터 argv[2], argv[3] 식으로 전달된다. boksa file1.txt file2.txt 명령으로 boksa명령을 실행했다면 이때 전달되는 argv 배열은 다음과 같다.

env

운영체제의 환경 변수를 알려 준다. 환경 변수는 운영체제마다 다르게 정의하는데 DOS의 경우 Path, Prompt 등이 있고 윈도우즈의 경우 컴퓨터 이름, 시스템 디렉토리 등의 정보들이 있다. 응용 프로그램에게 자신이 실행되는 환경을 알 수 있도록 해 준다는 의도로 전달되는 인수이나 이 인수가 아니더라도 환경 변수를 조사할 수 있는 다른 방법이 있기 때문에 실질적으로 사용되지 않는다. 무시해 버리자.