예외처리를 할 때는 throw 시 새로운 객체(예외처리용)가 힙 메모리에 생성된다.
그 후 예외처리를 실행한(던진) 함수는 스택에서 바로 pop된다. (반환과 다르게)
예외처리를 던지는 함수는 반환하지 않고 그 즉시 종료된다.
다음과 같이 divide 함수가 실행 될 경우 b==0을 만족하여 예외처리가 되고 31을 리턴하지 않고 함수가 그대로 종료 되어 f() 함수 역시 뒷 부분을 실행하지 않고 pop된다.
그 후 main에서 catch 구문을 통해 힙에 생성된 예외처리 객체에 접근이 가능해진다.
(catch 구문이 포인터 역할을 한다.)
스택이 예외처리를 만나 스택을 풀어헤치는 행위를 Stack unwinding이라고 부른다.
예외처리가 시작되면 throw부터 catch문이 있는 스택을 만날 때까지 스택을 계속 풀어헤치기 때문이다.
이를 스택을 되감는다고 말한다. catch 구문이 끝나면 예외처리 객체도 free 된다.
예외처리는 예외처리용 객체가 생성됨에 따라 오버헤드가 발생된다.
그래서 for 문과 같은 루프 문 안에는 throw 구문을 안 넣는게 추천된다.
예외처리를 하기 전 Stack unwinding 상에서 동적할당한 객체를 지우지 않을 경우 메모리 릭 문제가 발생한다. 위 사진은 f() 함수에서 Cat 객체를 동적할당 하였지만 delete 되기 전에 예외처리가 일어나 스택이 강제로 pop 되어 Cat 객체를 영영 지우지 못하는 메모리 릭 상태가 발생하였다.
이와 같이 스택 마지막에 delete 명령어를 실행하여 객체를 해제해줘야 하지만 중간에 예외처리가 발생하여 main의 catch 까지 jump한 관계로 결국 delete 명령문이 실행되지 못한 모습이다.
이를 방지하기 위해선 RAII를 준수하면서 코드를 짜야 한다.
대표적으로 스마트 포인터를 활용해야 한다.
unique_ptr<Cat> cp = make_unique<Cat>();
f() 함수에 원시 포인터 대신 스마트 포인터를 사용할 경우 f() 함수가 pop 되는 즉시에 해당 포인터의 소멸자가 자동으로 호출되어 힙에 동적할당 된 메모리를 해제한다.
스마트 포인터는 스택이 pop될 경우 그 스택에서 생성한 포인터의 소멸자가 호출되기 때문이다.
예외 처리(Exception Handling)은 Stack unwinding을 통해서 구현된다. (Handling : 처리, Handler : 처리자, event handler : 이벤트 처리자)
이 Stack unwinding은 일반적인 함수의 path와는 다른 path이기 때문에 (path : 함수의 실행<push, pop>경로,흐름)
약간의 오버헤드가 있을 수 있고, 일반적인 패스와는 다른 흐름을 보여주기 때문에 이를 고려하지 않으면 리소스 릭(메모리 릭)이 발생할 수 있다.
하지만 Stack unwinding 과정 중에도 스택안 객체의 소멸자(Destructor)는 호출해주기 때문에
예외가 발생한 경우라도 pop 되는 스택안 객체의 소멸자는 반드시 호출되므로 스마트 포인터로
소멸자를 호출하여 메모리 릭을 방지할 수 있다.
예외 처리를 사용할 경우 코드 자체가 Exception Safety를 만족하도록 해야한다.
memory leak 오타났어요!