메뉴 닫기

윈도우 시스템 크래시를 막는 구조적 예외 처리 방법 (__try, __except, __finally 완전정복)

윈도우 시스템 크래시를 막는 구조적 예외 처리 방법 (__try, __except, __finally 완전정복)

🛡️ WinAPI에서 꼭 알아야 할 예외 처리 구문, 안전한 프로그램을 만드는 열쇠입니다

윈도우 기반 애플리케이션을 개발하다 보면 예기치 않은 오류나 접근 위반으로 인해 프로그램이 강제로 종료되는 경우를 자주 마주하게 됩니다.
특히 파일 시스템, 메모리 접근, 하드웨어 통신 등 시스템 리소스를 다루는 작업일수록 이러한 위험은 더 높아지죠.
사용자에게 안정적인 프로그램을 제공하기 위해서는, 오류 발생 시에도 프로그램이 제 기능을 유지하고, 최소한 정상적으로 종료될 수 있도록 하는 예외 처리 메커니즘이 필수입니다.

이 글에서는 마이크로소프트 윈도우에서 제공하는 구조적 예외 처리(Structured Exception Handling, SEH)에 대해 알아봅니다.
__try, __except, __finally 구문을 활용하여 예외 상황을 직접 제어하고, 접근 위반(access violation) 같은 치명적인 오류를 안전하게 처리하는 방법을 단계별로 설명드릴게요.



🔎 구조적 예외 처리(SEH)란?

Windows 운영체제에서는 오류 발생 시 자동으로 예외를 감지하고 처리할 수 있는 구조적 예외 처리 메커니즘인 SEH(Structured Exception Handling)을 제공합니다.
이는 시스템 레벨에서 발생하는 심각한 오류들, 예를 들어 0으로 나누기, 잘못된 포인터 접근, 스택 오버플로우 같은 상황들을 감지하고 해당 예외를 코드에서 직접 제어할 수 있게 해줍니다.

C나 C++로 개발을 진행할 때 일반적인 try-catch 문법은 런타임 오류 수준의 예외만 처리할 수 있지만, SEH는 운영체제 커널과 연동되어 실행 시점에 발생하는 시스템 예외까지 포착할 수 있다는 점에서 강력한 기능을 자랑합니다.
이러한 처리 덕분에 프로그램이 강제 종료되는 상황을 방지하고, 사용자에게 적절한 오류 메시지를 출력하거나 대체 동작을 수행할 수 있게 됩니다.

SEH는 Windows에 특화된 기능이며, Visual C++ 환경에서 사용 가능한 키워드인 __try, __except, __finally를 통해 구현됩니다.
이는 일반 C 표준에서는 지원되지 않으며, Windows API 환경에서만 동작하는 특별한 예외 처리 방법입니다.

💡 TIP: SEH는 하드웨어 예외를 포착할 수 있는 거의 유일한 방법으로, 특히 드라이버나 시스템 API를 다룰 때 매우 유용하게 활용됩니다.

💬 SEH는 C++ 예외 처리와는 별도로 동작하며, C 코드에서도 사용할 수 있습니다.
try-catch가 처리하지 못하는 시스템 예외를 감지하고 직접 대처할 수 있도록 설계된 저수준 기능입니다.

🧩 __try, __except 구문의 구조와 역할

__try__except는 SEH에서 예외를 감지하고 처리하기 위한 핵심적인 키워드입니다.
이 구문은 예외가 발생할 수 있는 코드 블록을 감싸고, 실제 예외가 발생했을 때 어떤 방식으로 대응할지를 정의합니다.

기본적인 문법 구조는 다음과 같습니다.

CODE BLOCK
__try {
    // 예외가 발생할 수 있는 코드
}
__except(EXCEPTION_EXECUTE_HANDLER) {
    // 예외 발생 시 실행되는 코드
}

이때 __try 블록 안에서 예외가 발생하면, 시스템은 __except 블록으로 제어를 넘깁니다.
EXCEPTION_EXECUTE_HANDLER는 고정된 상수이며, 이를 통해 예외 발생 여부에 상관없이 항상 except 블록이 실행되도록 지정할 수 있습니다.

🛠️ 필터 함수로 예외 분기 처리하기

__except 뒤에 오는 괄호에는 필터 함수 또는 조건식을 작성할 수 있습니다.
이 필터는 예외 코드에 따라 다른 처리를 가능하게 해주며, 보다 정교한 예외 제어가 가능합니다.
예를 들어, 특정 예외 코드만 처리하고 나머지는 무시하도록 설정할 수 있죠.

CODE BLOCK
__except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
         EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
    // 접근 위반일 경우에만 실행
}

💎 핵심 포인트:
__except는 단순한 catch가 아닙니다. 예외 코드에 따라 세부적으로 분기 처리할 수 있는 고급 기능입니다.



🔄 __finally 구문과 리소스 정리

예외가 발생하든 하지 않든, 항상 실행되어야 하는 코드는 __finally 블록 안에 작성합니다.
이는 파일 핸들, 메모리, 네트워크 연결 등 시스템 리소스를 해제하거나 마무리 작업을 처리하는 데 매우 유용합니다.

SEH에서 __finally 키워드는 __try와 함께 사용할 수 있으며, __except 대신 쓰는 것이 아니라 목적에 따라 구문을 선택하여 조합해야 합니다.
즉, 예외를 처리하려면 __except를, 반드시 실행해야 할 종료 코드는 __finally에 작성하는 식이죠.

CODE BLOCK
__try {
    // 리소스를 사용하는 코드
}
__finally {
    // 반드시 실행되는 리소스 정리 코드
}

예를 들어 파일을 열고 데이터를 쓰는 도중 예외가 발생하더라도, __finally 블록은 실행되어 파일 핸들을 안전하게 닫을 수 있습니다.
이를 통해 리소스 누수나 메모리 오류를 예방하고, 시스템 안정성을 크게 향상시킬 수 있습니다.

🧹 예외 여부와 무관하게 마무리 처리

__finally 블록은 예외가 발생하든, 정상적으로 코드가 종료되든 항상 실행된다는 점에서 매우 중요합니다.
try 블록 안에서 return이 되더라도 finally 블록은 실행되며, 코드의 예측 가능성을 높여줍니다.

💡 TIP: __finally는 단독으로도 사용할 수 있지만, __try와 함께 예외 여부와 상관없이 정리 작업을 수행할 때 진가를 발휘합니다.

🚫 접근 위반(access violation) 직접 처리하기

윈도우 환경에서 가장 흔하게 발생하는 치명적 예외 중 하나는 접근 위반(Access Violation)입니다.
이는 잘못된 포인터를 참조하거나, 보호된 메모리에 접근하려 할 때 발생하며, 아무런 처리 없이 발생하면 프로그램은 곧바로 강제 종료됩니다.

하지만 SEH를 사용하면 이러한 접근 위반도 감지하여 예외 처리 루틴으로 우회시킬 수 있습니다.
다음은 잘못된 포인터 접근을 SEH로 잡아내는 예시입니다.

CODE BLOCK
__try {
    int* p = NULL;
    *p = 100;  // 접근 위반 발생
}
__except(GetExceptionCode() == EXCEPTION_ACCESS_VIOLATION ?
         EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
    MessageBox(NULL, L"접근 위반 예외 발생!", L"오류", MB_OK);
}

위 코드에서 NULL 포인터를 역참조하면 접근 위반 예외가 발생합니다.
하지만 SEH를 통해 해당 예외를 감지하고, 사용자에게 메시지 박스를 출력한 뒤 안전하게 종료할 수 있게 됩니다.
이처럼 SEH는 시스템 크래시를 막고, 예외 발생 시 사용자 경험을 유지할 수 있는 방어 코드로 활용됩니다.

⚠️ 주의: 모든 예외를 SEH로 무작정 처리하는 것은 위험할 수 있습니다.
치명적인 오류를 억지로 숨기면, 문제의 원인을 찾기 더 어려워질 수 있습니다.
꼭 필요한 예외만 포착하도록 필터를 정교하게 설정하세요.



💡 예외 처리 시 자주 하는 실수와 주의점

SEH는 강력한 도구지만, 잘못 사용할 경우 오히려 디버깅을 어렵게 하거나, 의도치 않게 오류를 숨기는 결과를 초래할 수 있습니다.
실제로 예외 처리를 무분별하게 사용하면 프로그램의 안정성은 물론 성능에도 부정적인 영향을 줄 수 있죠.
따라서 아래의 실수들을 꼭 피하는 것이 좋습니다.

  • ⚠️모든 코드에 SEH를 감싸는 실수 – 예외 가능성이 없는 부분까지 SEH를 적용하면 오히려 성능 저하 유발
  • 🧱__except에 빈 블록 작성 – 예외는 감지했지만 아무 조치도 하지 않는다면 의미가 없음
  • 🔍예외 코드를 구분하지 않고 무조건 처리 – 다양한 오류에 동일한 대응은 오히려 문제의 원인을 은폐할 수 있음
  • 🌀__finally에서 return 또는 exit 호출 – 후속 작업이 누락될 수 있으므로 자제 필요

또한 디버깅 중 SEH가 예외를 먹어버리는 문제를 겪은 경험이 있다면, Visual Studio 설정에서 Debug → Windows → Exception Settings를 통해 예외 중단 조건을 직접 설정할 수 있습니다.
이는 개발 단계에서 오류를 정확히 추적하고, 불필요한 SEH 처리를 줄이는 데 매우 유용하죠.

💡 TIP: SEH는 안정성을 높이기 위한 도구이지, 모든 오류를 숨기기 위한 방패가 아닙니다. 진짜 문제는 반드시 로그를 남기고 수정해야 합니다.

자주 묻는 질문 (FAQ)

__try와 try/catch는 뭐가 다른가요?
__try는 구조적 예외 처리(SEH)를 위한 키워드이며, try/catch는 C++의 표준 예외 처리입니다.
SEH는 하드웨어 예외까지 다룰 수 있다는 점에서 더 저수준에 가깝습니다.
모든 프로그램에 SEH를 적용해야 하나요?
아닙니다.
모든 코드에 SEH를 적용하는 것은 비효율적이며, 실제로 예외가 발생할 가능성이 높은 코드에만 적용하는 것이 좋습니다.
__finally는 왜 필요한가요?
예외 발생 여부와 관계없이 반드시 실행되어야 할 코드를 정의하기 위해 __finally 블록이 필요합니다.
리소스 정리나 종료 처리에 유용합니다.
SEH는 리눅스에서도 쓸 수 있나요?
SEH는 Windows 플랫폼에 특화된 기능으로, Linux 환경에서는 사용할 수 없습니다.
대신 POSIX 신호 처리나 C++ 예외 처리 등을 사용합니다.
__except 안에서 어떤 작업을 할 수 있나요?
로그 기록, 사용자 알림, 리소스 해제, 상태 복구 등 다양한 예외 처리 작업을 수행할 수 있습니다.
단, 지나치게 복잡한 로직은 피하는 것이 좋습니다.
Visual Studio에서는 SEH를 어떻게 디버깅하나요?
Exception Settings 창을 통해 특정 예외 발생 시 중단하도록 설정할 수 있습니다.
이 기능을 활용하면 SEH가 예외를 숨기기 전에 디버깅할 수 있습니다.
__try 안에서 return 해도 __finally가 실행되나요?
네, return이 호출되더라도 __finally 블록은 반드시 실행됩니다.
이는 리소스 정리나 안전한 종료 처리를 보장하는 데 도움이 됩니다.
SEH로 모든 예외를 안전하게 처리할 수 있나요?
대부분의 하드웨어 예외는 처리할 수 있지만, 논리 오류나 외부 의존성 문제 등은 코드 구조나 논리 개선이 필요합니다.
SEH는 그 자체로 만능은 아니며, 보조 도구로 사용해야 합니다.

🧠 안정성과 예외 복구를 모두 잡는 SEH의 활용법

Windows API 환경에서 개발되는 프로그램이라면, 시스템 오류나 예외 상황을 감지하고 우아하게 처리하는 능력은 필수입니다.
구조적 예외 처리(SEH)는 단순한 try-catch를 넘어, 하드웨어 수준의 예외까지도 제어할 수 있도록 돕는 중요한 기능입니다.
__try, __except, __finally를 적절히 활용하면 시스템 자원을 안전하게 보호하고, 예상치 못한 상황에도 프로그램을 안정적으로 종료할 수 있게 됩니다.

하지만 아무 예외나 무조건 잡는 것이 능사는 아닙니다.
필터 함수를 통해 어떤 예외를 처리할지 신중히 분기하고, 반드시 필요한 곳에만 SEH를 적용해야 프로그램의 성능과 유지보수성을 해치지 않습니다.
이제 여러분도 SEH의 구조와 사용법을 이해했다면, 더욱 신뢰할 수 있는 소프트웨어를 개발하는 데 한 발 더 가까워졌을 것입니다.


🏷️ 관련 태그 : WinAPI, 구조적예외처리, SEH, __try__except, 접근위반예외, 시스템오류, 윈도우개발, 예외처리기법, 안정성코딩, C프로그래밍