non-main 쓰레드가 초기 호출 함수에서 벗어나면
새로운 쓰레드가 초기 호출 함수에서 벗어나면 std::exit
함수가 역시 호출되고, std::exit
함수 내부에서 쓰레드 실행 과정에서 정상적인 과정을 거쳐 생성된 thread storage duration 객체의 소멸자가 호출된다.
main 쓰레드가 main 함수에서 벗어나면
main
함수를 벗어나면, 가장 먼저 main
함수의 반환값을 argument로 std::exit
함수를 호출한다. std::exit
함수 내부에서 정상적인 과정을 거쳐 생성된 static storage duration 객체 생성 시점 또는 std::atexit
함수로 등록한 함수를 전체적으로 시간 순서로 배열한 역순으로 소멸자나 등록 함수를 번걸아 호출 완료한다.
thread stroage duration 객체과 static storage duration 객체에 대한 생성자/소멸자
static storage duration 객체의 초기화는 크게 static initialization
과 dynamic initialization
으로 구분된다. 두 가지 유형 중 한 방식으로 먼저 초기화가 완료된 객체는 역순으로 소멸자 호출를 완료한다.
모든 thread storage duration 객체는 static storage duration 객체보다 소멸자 호출를 먼저 완료한다.
배열 type이나 class type 객체의 subobject 생성 과정에서 생성한 static storage duration의 block 변수를 생성하더라도, static storage duration의 block 변수의 생성보다 subobject의 소멸자 호출을 먼저 완료한다.
#include <print>
class A {
struct nested_A {
int val;
nested_A(int v) :val{ v } {};
~nested_A() {
std::println("nested_A destructor: {}", val);
}
};
nested_A nested_;
public:
A(int v) :nested_{ v } {
static nested_A local_a{ v * 100 };
};
};
int main() {
A b { 1 };
return 0;
}
[출력 결과]
nested_A destructor: 1
nested_A destructor: 100
static duration 또는 thread duration 객체의 소멸자 호출 과정에서 exception으로 인해 소멸자 호출을 완료하지 않고 벗어나면, std::terminate
함수가 호출된다.
static 또는 thread storage duration의 소멸자에서 함수를 호출하고, control flow가 이미 소멸된 static 또는 thread storage duration의 block 변수를 통과하거나, 간접적으로(예를 들어 포인터를 통해) 사용하면, undefined behavior다.
static storage duration 객체의 초기화가 std::atexit
함수 호출보다 먼저 완료하면, std::atexit
로 등록한 함수 호출을 객체 소멸자 호출보다 먼저 완료한다. 반대로 std::atexit
함수 호출을 static storage duration 객체의 초기화보다 먼저 호출하면, 객체 소멸자 호출을 std::atexit
로 등록한 함수 호출보다 먼저 완료한다.
std::atexit
함수로 등록한 함수를 등록 역순으로 호출한다.
#include <print>
class A {
int val_{};
public:
A(int i) :val_{ i } {}
~A() { std::println("~A:{}", val_); }
};
void reg1() {
std::println("reg1");
}
void reg2() {
std::println("reg2");
}
class B {
public:
B() {
std::atexit(reg1);
std::atexit(reg2);
}
};
A a{ 1 };
B b{};
A a100{ 100 };
int main() {
return 0;
}
[출력 결과]
~A:100
reg2
reg1
~A:1
static storage duration 객체의 소멸자과 std::atexit
로 등록한 함수 호출 과정에서 사용할 수 있다고 명시하지 않는 표준 라이브러리의 객체나 함수를 signal handler에서 사용하면, undefined behavior다. static storage duration 객체의 소멸자 호출이 완료된 이후에, 해당 객체를 사용하면 undefined behavior다. std::exit
호출하기 이전 또는 main
함수에서 벗어나기 이전에 모든 쓰레드를 종료한 경우, static storage duration의 소멸자가 호출되지 않은 상태에서는 이들 객체에 대한 접근은 undefined behavior가 아니다.
std::abort 함수
std::abort
함수 호출은 std::atexit
나 std::at_quick_exit
함수로 등록한 함수나, 생성한 객체의 소멸자 호출을 수행하지 않고, 프로그램을 곧바로 종료한다.
std::terminate 함수
std::terminate
함수는 C++ runtime
에서 지원하는 함수다. 즉, C++에서 정의한 함수다. exception 제어 과정에서 구현상의 미묘한 문제로 인해, 더 이상 exception를 제어할 수 없는 상태가 되면, std::terminate
함수를 호출하도록 설계했다. std::terminate
함수는 C++의 exception 제어과 연관된 함수다.
exception 제어의 미묘한 문제로 발생한 std::terminate
함수 호출 이전에 stack unwound 수행할지 여부는 구현 컴파일러마다 다르다. noexcept
함수가 exception하면 std::terminate
를 호출되는데, std::terminate
함수 호출 이전에, stack unwound를 온전히 수행할지, 부분적으로 수행할지, 전혀 수행하지 않을지 여부는 구현 컴파일러가 선택한다.
지금까지 언급하지 않는 경우로 인한 std::terminate
함수 호출 이전에, stack unwound를 수행하지 않아야 한다. 구현 컴파일러는 std::termination
함수 호출이 최종적으로 프로그램을 종료한다는 이유로 stack unwound를 수행하도록 과도하게 구현하지 않아야 한다.
'프로그래밍 언어 > C++' 카테고리의 다른 글
value category의 서로 다른 유형간 변환 (0) | 2024.02.05 |
---|---|
function과 value category 이해하기 (0) | 2024.02.02 |
value category 이해하는 첫 단추 (0) | 2024.02.01 |
range-based for 구문 이해하기 (1) | 2024.01.28 |
C++의 main 함수에 대해 (0) | 2024.01.25 |