개요

std::exception에 대한 논란은 언제나 많다. 제대로 문제에 대해서 이야기하기 위해선 내부구현을 알아야 한다고 생각해서 파봤다.

std::exception은 정확히 무얼 하는가

std::exception이 하는 일은 단순하지 않다. try-catch가 있는 호출자 함수를 찾아 스택프레임을 하나하나 unwind하면서 올라가야 한다.

1.먼저 현재 함수에 catch가 있는지 체크한다, 2.없으면 현재 함수를 끝내야 하므로 지역변수의 소멸자들을 호출시킨다. 3.이전 함수의 스택 상태로 복구시킨다. (반복)

Untitled

3번은 쉽다. 단순히 푸시된 BP와 Ret_addr을 이용해 이전함수의 스택 상태를 복구하고 돌아갈 수 있다. (그냥 C언어의 return 과정을 여러번 반복하는거다. add esp, pop ebp, ret)

하지만 1번과 2번은 기존 스택의 정보로는 불가능하다. 스택 정보로는 이게 어떤 함수인지 알 수 없고 어떤 변수들의 소멸자를 호출시켜야하고 catch가 있는지 없는지에 대한 정보가 스택에 존재하지 않기 때문이다.

따라서 해당 정보들을 추가적으로 어딘가에 기록하여 추적하는 방식을 사용해야한다.

구현 방식

먼저, std::exception은 표준에서 내부구현에 대해 정의하지 않는다. 따라서 컴파일러마다, 머신 마다 다른 구현이 존재함을 유의하자.

구현 방식은 크게 2가지 형식이 있다.

  1. 런타임의 함수 시작과 끝마다 현재 함수의 정보를 기록해서 스택을 추적하다 throw가 발생하면 함수를 unwind 하는 방법. (VC++ 32bit, GCC 과거 버젼, SEH)

  2. 아무런 스택 추적을 하고 있지 않다가 throw가 발생하면 RTTI처럼 함수를 특정하고 unwind 하는 방법. (VC++ 64bit, GCC 최신 버젼)

구현 방식 1. Dynamic Registration

1번이 기존 컴파일러들이 많이 사용하던 방식이다. 런타임에서 모든 함수의 정보를 추적하므로 try를 사용하는 것 만으로도 매 함수 호출마다 오버헤드가 발생하는 문제가 있다.

Untitled

실제로 VC++ 32bit에서 try-catch를 사용하면 도입부에서 SEH를 세팅하는 것을 볼 수 있다. 스크린샷은 유영천님의 블로그에서 가져왔다.

Untitled

그리고 Throw 부분을 보면 NtRaiseException 시스템 서비스를 호출한다.

Untitled

매 함수 프레임마다 별도의 Exception Handler에 대한 정보를 기록하여 추적하다가 Throw가 발생되면 시스템 콜로 핸들링 하는 방식이다.