25. try/finally 스택 되감기와 예외 필터(when)의 실행 시점 차이
난이도 최상내 리뷰 · C#
해설 · C#
해설 — try/finally 스택 되감기와 예외 필터(when)의 실행 시점 차이
난이도: 최상
요약
C#의 예외 필터(catch (Ex e) when (condition))는 스택 되감기(stack unwinding)가 시작되기 이전, 예외가 발생한 바로 그 콜스택이 아직 살아있는 상태에서 평가된다. 필터가 false를 반환하면 스택은 되감기지 않고 예외는 그대로 상위로 전파(다른 필터/핸들러 탐색)되며, 필터가 true를 반환해야 비로소 되감기가 시작되고 그제서야 그 사이의 finally 블록들이 실행된 뒤 catch 본문이 실행된다. 따라서 problem.cs를 실행하면 [filter]가 [finally]보다 먼저 출력된다.
핵심 개념
- 스택 되감기(Stack Unwinding): 예외를 처리할 핸들러가 확정된 뒤, 예외 발생 지점부터 그 핸들러까지의 콜스택에 있는 모든
finally블록을 실행하며 스택 프레임을 정리해 나가는 과정. - 예외 필터(Exception Filter):
when절의 조건식. CLR이 예외 처리 핸들러를 "탐색"하는 1차 패스(first-pass) 동안 평가되며, 이 시점에는 아직 스택이 되감기지 않은 상태다. - 2-패스 예외 처리 모델: CLR의 예외 처리는 (1) 핸들러를 찾는 탐색 패스 — 이 안에서 필터가 평가됨 — 와 (2) 핸들러가 확정된 후 스택을 되감고 실제로 실행하는 패스, 두 단계로 나뉜다.
단계별 내부 동작
Main()→ProcessPacket(1)→ 내부try→HandlePacket(1)호출.[handle] packet=1 처리 시작출력.HandlePacket안에서throw new InvalidOperationException(...)실행. 이 시점부터 CLR의 예외 탐색(1차 패스)이 시작된다.- CLR은 콜스택을 위로 올라가며 이 예외를 처리할 수 있는
catch를 찾는다. 먼저 안쪽try에는catch가 없고finally만 있으므로 그냥 지나친다(아직 실행하지 않음 — finally 실행은 되감기 패스에서만 일어난다). - 바깥쪽
try의catch (Exception e) when (LogAndCheck(e))에 도달하면, 되감기 전에 필터 조건식을 평가한다. 이때LogAndCheck(e)가 호출되어[filter] 필터 평가 중... (e=InvalidOperationException)이 출력된다. 이 시점의 호출 스택에는 아직HandlePacket, 예외가 던져진 지점까지의 프레임이 그대로 남아 있다(스택이 아직 되감기지 않았기 때문). LogAndCheck가true를 반환하므로 이catch가 핸들러로 확정된다. 이제 CLR은 2차 패스(되감기)를 시작해, 예외 발생 지점부터 이catch까지 사이에 있는finally블록들을 실행한다 — 여기서 내부try의finally가 실행되어[finally] 패킷 처리 종료 (내부 리소스 정리)가 출력된다.- 되감기가 끝난 뒤 비로소
catch본문이 실행되어[catch] 예외 처리: 잘못된 패킷 형식이 출력된다. - 따라서 정확한 출력 순서는 다음과 같다:
[handle] packet=1 처리 시작 [filter] 필터 평가 중... (e=InvalidOperationException) [finally] 패킷 처리 종료 (내부 리소스 정리) [catch] 예외 처리: 잘못된 패킷 형식
흔한 오해·함정
- "
finally는 예외가 발생하면 무조건 catch보다 먼저, 그리고 필터보다도 먼저 실행될 것"이라고 오해하기 쉽다. 실제로는 필터 평가가finally보다 먼저 일어난다 — 필터는 "이 핸들러가 이 예외를 잡을지 말지"를 결정하는 탐색 단계의 일부이고,finally는 핸들러가 확정된 뒤의 정리 단계에 속하기 때문이다. - 필터 함수(
LogAndCheck) 안에서Environment.StackTrace를 찍거나 디버거로 콜스택을 보면, 일반catch블록 안에서 보는 것보다 훨씬 깊은(예외가 실제로 던져진 지점에 가까운) 스택을 보게 된다 — 이는 필터가 되감기 이전에 실행되기 때문이며, 크래시 덤프 분석이나 원인 추적에 유용하게 활용되기도 한다(예:when (LogFullStackAndReturnFalse(e))패턴으로 원본 스택을 로깅한 뒤 항상 false를 반환해 다른 핸들러로 넘기는 기법). - 반대로, 필터 안에 부작용(side effect)이 있는 로직 — 예: 카운터 증가, 외부 리소스 접근 — 을 넣으면, 아직
finally로 정리되지 않은 리소스 상태에서 그 로직이 실행되는 셈이 되어 예상치 못한 순서 의존 버그를 만들 수 있다. 특히 필터가 여러 번 평가될 수 있는 상황(여러catch절 후보가 있을 때 순서대로 필터가 시도됨)에서는 필터 부작용이 여러 번 실행될 수도 있다는 점도 주의해야 한다.
면접 포인트
- 예외 필터(
when)의 평가 시점이 스택 되감기 이전이라는 것과, 그로 인한 정확한 실행 순서(필터 → finally → catch)를 근거를 들어 설명할 수 있는가. - 이 특성이 디버깅/로깅(원본 스택 보존)에 어떻게 실무적으로 활용될 수 있는지 예를 들 수 있는가.
- 필터 안에 부작용이 있는 코드를 넣었을 때 생길 수 있는 문제(정리되지 않은 리소스 상태에서 실행됨, 여러 번 평가될 수 있음)를 지적할 수 있는가.