C 언어에서 `defer`를 흉내내는 다양한 방법들: 장단점과 보안 문제
C 언어 표준에 `defer` 기능이 없어, 개발자들은 다양한 매크로 기반 구현(Macro-based Implementation)을 시도함
GCC, Clang, MSVC 등 컴파일러별 지원(Compiler Support)의 차이로 인해 이식성(Portability) 문제가 발생함
GCC의 실행 가능한 스택(Executable Stack) 사용은 보안 취약점(Security Vulnerability)을 야기할 수 있음
C++의 RAII(Resource Acquisition Is Initialization), Rust의 `defer`와 비교하며 C 언어의 한계를 지적하는 의견도 존재함
C23 표준 제안과 GCC의 `defer` 구현
C23 표준에 `defer` 기능이 포함되지 않아, 개발자들은 GCC의 `cleanup` 속성 및 중첩 함수(Nested Functions)를 활용한 구현을 시도했다. 이 방식은 `defer`의 동작을 흉내낼 수 있지만, GCC에 종속적이며, 실행 가능한 스택(Executable Stack)을 사용해야 한다는 단점이 있다. 실행 가능한 스택(Executable Stack)은 버퍼 오버플로우(Buffer Overflow) 공격에 취약하여 보안 문제를 야기할 수 있다는 점이 지적된다.
Clang과 MSVC에서의 `defer` 구현
Clang은 GCC의 중첩 함수를 지원하지 않지만, 블록(Blocks) 확장을 통해 `defer`를 구현할 수 있다. 하지만, `-fblocks` 옵션으로 컴파일해야 하고, 블록 종료 후 `;`를 추가해야 하는 등 추가적인 주의사항이 존재한다. MSVC는 `cleanup` 속성을 지원하지 않지만, `__try`와 `__finally` 구문을 사용하여 유사한 기능을 구현할 수 있다. 하지만, 이는 `defer`와는 다른 동작 방식을 가지며, 윈도우 환경에서만 유효하다.
다양한 `defer` 구현 방식의 비교
본문에서는 `setjmp`와 `longjmp`를 사용한 구현, `for` 루프를 활용한 구현, 그리고 스택 프레임을 사용하는 구현 등 다양한 `defer` 구현 방식을 소개한다. 각 방식은 컴파일러 호환성, 보안, 그리고 코드 복잡성 측면에서 트레이드오프(Trade-offs)를 가진다. 특히, `for` 루프를 사용한 구현은 모든 컴파일러에서 동작하지만, `break`나 `return` 시 `defer`가 실행되지 않는다는 단점이 있다.
C++의 RAII와 Rust의 `defer`
일부 댓글에서는 C++의 RAII(Resource Acquisition Is Initialization), Rust의 `defer`와 비교하며 C 언어의 한계를 지적한다. C++의 RAII는 객체의 소멸자를 통해 자원 해제를 자동화하며, Rust의 `defer`는 코드 블록 종료 시 특정 코드를 실행하도록 보장한다. 이러한 기능들은 C 언어에서 매크로 기반의 `defer` 구현보다 안전하고 간결한 코드를 작성할 수 있도록 돕는다.