티스토리 뷰

반응형

C++에서 프로그램을 종료할 때, 어떤 것을 써야 할까?

 

여러 방법이 있지만, 가장 표준적인 방법은 int main()과 함께 따라오는 return 0; 구문이다.

 

1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
 
int main() {
    
    // do_something
 
    return 0// finish the program
}
cs

 

그러나, 종료하는 방법 중 exit(0)를 활용하는 방법도 있다. 그럼, 다음과 같이 적으면 안되나? 왜 이 방법은 표준이 되지 않았을까? 

 

1
2
3
4
5
6
7
8
9
#include <iostream>
using namespace std;
 
void main() {
    
    // do_something
 
    exit(0); // finish the program
}
cs

 

이번 포스팅에서는 이에 대하여 알아보고, 왜 return 0;가 표준이 되었는지 살펴보자.

thumbnail
return 0;에 대하여 탐구하여보자.


1. return 0 의 뜻

C / C++에서 프로그램을 종료할 때, 무의식적으로 쓰는 구문은 main함수의 끝에 붙어있는 'return 0;' 이다.

 

이에 대한 뜻은, 프로그램을 끝내고 난 뒤 정수 '0'을 반환한다는 뜻이다. 근데, 이 함수는 어디로 반환하는 것일까?

 

이에 대한 글은 인터넷에 많이 있지만, 그래도 이 글에서 필요하기에 정리하겠다.

 

'return 0;' 이란 것은 현재 운영체제에게 해당 '코드 0'을 전달한다는 의미이다. 운영체제의 쉘에서는 0이 True값이므로, 아무 에러 없이 함수를 종료했다는 의미를 신호로 전달하는 것이다.

 

만약 함수 실행 중 에러 코드가 있다면 '코드 xx' 등 다른 수가 전달될 것이고, 운영체제는 이를 에러로 간주할 것이다.

 

현대 C / C++에서는 프로그램이 잘 종료되었다는 것은 운영체제에게 암시적으로 전달한다. 즉 return 0; 을 생략하여도 문제는 없다. 그러나 프로그램의 안정성을 위해서 return 0;을 계속 써는 것이 하나의 관례처럼 이어지고 있는 것이다. (C의 경우 C99 표준부터, C++의 경우 모든 버전에서 암시적 종료가 가능하다.)

 

헤더 <stdlib.h> / <cstdlib.h> 에서는 매크로 EXIT_SUCCESS = 0, EXIT_FAILURE = 1 이 정의되어 있으므로, return 0 대신 return (EXIT_SUCCESS); 도 가능하다.


2. return 0; 이외의 종료 함수들

C / C++에서는 return 0; 이외에도 프로그램을 종료할 수 있는 함수들이 여럿 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;
 
int main() {
    
    // do_something
 
    // finish the program
    return 0
    exit(0);
    _Exit(0);
    quick_exit(0);
    terminate();
    abort();
    throw exception();
}
cs

 

exit이나 exception을 띄우는 것으로 프로그램을 종료할 수도 있다. C++의 코드를 보면 exit(0)으로 프로그램을 끝내는 문법을 본 적이 있지 않은가?

 

return 0과 exit(0)은 같은 역할을 한다. 예외를 띄워 프로그램을 끝내는 것도 정상적인 종료는 아니지만 그래도 프로그램을 종료하는 역할을 한다. 그럼, 무슨 차이가 있을까? 그리고 이떤 점에서 exit(0)은 C++의 표준에서 밀려난 걸까?


3. RAII 원칙과 스택 되감기(stack unwinding)에 관하여

C / C++에는 RAII 원칙이 존재한다. (Resource Acquision Is Initialization, 이에 대한 구체적인 설명은 하이퍼링크를 참조하라.)

 

쉽게 말해, 어떤 객체를 새로 생성할 때, 생성자(constructor)안에서 초기화 및 자원 할당을 해야하고, 소멸자(destructor)에서 사용한 자원을 돌려주어야 한다는 것이다.

 

만약 어떤 file.txt을 출력한다고 가정하면, 생성자에서 파일을 읽어야 하고, 소멸자에서 파일을 닫고 사용한 자원을 다시 운영체제에 돌려주어야 한다.

 

근데 만약 소멸자에서 그냥 file.txt 닫고 끝낸다면? 이런 경우가 쌓이다보면 스택(stack) 영역 안에서 불필요하게 공간을 차지하는 데이터가 많을 것이다.

 

이런 현상이 반복되면 비효율을 초래할 것이고, 따라서 소멸자를 호출할 때는 반드시 사용했던 자원이나 영역을 자동, 혹은 수동으로 풀어주어야 한다. 이를 스택 되감기(Stack Unwinding)라 한다.

 

자주 사용하는 종료 코드를 응용하여 다음 코드를 관찰하여보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <exception>
#include <fstream>
#include <memory>
 
using namespace std;
 
void inner_func() {
    throw exception();
}
 
void func() {
    auto ptr = make_unique<int>();
    inner_func();
}
 
int main() {
    
    ofstream os("file.txt");
    os << "Something";
 
    int exit_case; /* 1, 2, 3, 4 */
 
    switch (exit_case) {
        case 1:
            return 0;
        case 2:
            throw exception();
        case 3:
            func();
        case 4:
            exit(0);
    }
}
cs

 

각 경우에 대하여 종료할 때 어떤 일이 일어나는지 살펴보자.


1] return 0;

함수가 모두 정상적으로 종료된 경우 현재 함수에 대한 소멸자를 모두 호출한다. 따라서, os의 소멸자를 모두 호출하며, "file.txt"에 관련된 스택공간을 모두 비운 후, 디스크로 반환한다.

 

 

이 것이 C / C++의 종료 표준이며, 가장 권장되는 방법이다. 운영체제가 어떻게 끝나는 지 알 수 있고, 필요할 경우 종료 코드에 따라 직접 여러 에러를 처리할 수 있다.


2] throw exception();

이런! 예외를 맞닥드렸다. 다행인건, 예외를 맞닥드리면 함수 소멸자를 호출하며, 예외 처리를 진행할 때 사용한 스택 영역을 반납한다는 것이다.

 

그러나, 구현된 코드에 따라 스택 반납을 할 수도, 안 할수도 있다.

 

try...catch...throw구문을 생각해보면, try문에서 에러가 발생할 경우, catch 구문이 실행된다는 것을 알 수 있다. 이 경우 catch 구문이 끝나면 해당 스택 영역은 반납될까?

 

만약, 예외 처리에 대한 핸들러가 없다면, 함수 std::terminate()를 호출하고, 이 경우 스택을 반납할 지 반납하지 않을 지는 코드에 따라 다르다.

 

따라서, exception의 경우 직접 스택 반납을 해줘야 할 수 있다.

 

스택을 안전하게 반납 위해선, 1번의 return문을 활용하여 작성할 수 있다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
using namespace std;
 
int main() {
    
    try {
        // do_something
    } catch(const exception&) {
        // 비정상 종료를 반환하도록 하자.
        return EXIT_FAILURE;
    }
 
}
cs

 

이 경우 예외 처리시 안전하게 모든 스택 영역에 사용한 메모리를 반납할 수 있다.


3] inner_func()

이 경우 2번 경우에서 여러 함수가 추가되었지만, 실제 예외처리를 진행하면 inner_func()와 func()에 대한 스택 영역(ptr)이 모두 반납되는 거을 확인할 수 있다.

 

궁금하면 직접 exit_case = 3을 넣어보자.


4] exit(0);

C / C++에서 프로그램을 종료할 경우, exit(0)을 사용하는 경우가 있다.

 

최악의 방법이다. 말 그대로 프로그램 종료만 시켜주므로, 사용한 스택 영역은 모두 그대로 남아있으므로, 만약 "file.txt"를 다른 프로그램에서 수정하려 해도 죽은 프로그램 때문에 수정이 불가능하다. 따라서, 최대한 피해야 하는 방법이다.

 

정확히 말하자면, 프로그램 종료 후, std::atexit 핸들러를 호출한다. 이 경우 프로그램 종료 시 어떤 동작을 할 수 있을 지 정해준다. 그러나, 자동으로 스택을 반납하지 않는다.


5] 그 외 경우

그 외의 경우 언급하지 않은 함수들이 있다. (std::_Exit, std::quick_exit, std::abort, std::terminate...)

 

잘 사용하지는 않지만, 프로그램 종료에는 문제가 없는 함수이지만 스택 되감기를 진행하지 않으므로 되도록 사용을 지양해야 한다.


4. 결론

결론적으로, 특별한 이유가 없으면 return 0;으로 프로그램을 종료하자. 만약 예외처리가 필요하다면, catch문을 이용하여 에러를 처리하면 된다.

 

도움이 되었다면 지나가는 길에 하트 하나 눌러주세요, 양질의 글을 쓰는데 하나의 동기부여가 됩니다😍

지적이나 오타 수정 댓글 환영합니다!!

 

 

반응형
댓글
Total
Today
Yesterday
공지사항
최근에 올라온 글
최근에 달린 댓글
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함