Safe c.h
C에 초능력을 부여하기 - 사용자 정의 헤더 파일 (safe_c.h)
About
- safe_c.h는 C 언어에 C++과 Rust의 안전성과 편의 기능을 추가하는 600줄짜리 사용자 정의 헤더 파일로, 메모리 누수 없는 스레드 안전 grep(cgrep) 구현에 사용됨
- RAII, 스마트 포인터, 자동 정리(cleanup) 속성을 통해 수동 free() 호출 없이 자원 관리 자동화
- 벡터, 뷰, Result 타입, 계약 매크로 등으로 버퍼 오버플로, 오류 처리, 전제 조건 검증을 안전하게 수행
- 뮤텍스 자동 해제, 스레드 스폰 매크로, 분기 예측 최적화 등으로 동시성과 성능을 유지하면서 안전성 확보
- 결과적으로 동일한 성능(-O2 수준)으로 누수·세그폴트 없는 C 코드 작성 가능성을 입증
safe_c.h 개요
- safe_c.h는 C++과 Rust의 기능을 C 코드에 이식하는 헤더 파일
- C23의 cleanup 속성을 지원하지 않는 컴파일러(GCC 11, Clang 18 등)에서도 동일한 RAII(자동 정리) 동작 제공
- CLEANUP(func) 매크로로 함수 종료 시 자원 자동 해제
- LIKELY()와 UNLIKELY() 매크로로 핫패스 분기 예측 최적화
메모리 관리: UniquePtr과 SharedPtr
- UniquePtr은 단일 소유 스마트 포인터로, 스코프 종료 시 자동으로 free() 호출
- AUTO_UNIQUE_PTR() 매크로로 선언 시, 오류 발생이나 조기 반환에도 메모리 자동 해제
- shared_ptr_init()과 shared_ptr_copy()로 참조 증가·감소 자동 처리
- 스레드 간 안전한 공유 구조체 관리에 사용
버퍼 오버플로 방지: Vector와 View
- DEFINE_VECTOR_TYPE() 매크로로 타입 안전한 자동 확장 벡터 생성
- 재할당, 용량 관리, 정리(cleanup)를 자동 처리
- AUTO_TYPED_VECTOR()로 선언 시 스코프 종료 시 자동 해제
- DEFINE_SPAN_TYPE()으로 타입별 Span 정의
- 경계 검사 포함으로 안전한 배열 접근 보장
오류 처리: Result 타입과 RAII
- Result 구조체는 Rust의 Result
와 유사한 성공/실패 구분형 반환 타입 - DEFINE_RESULT_TYPE()으로 타입별 결과 구조 생성
- RESULT_IS_OK()와 RESULT_UNWRAP_ERROR()로 명확한 오류 처리
- AUTO_MEMORY() 매크로로 malloc된 메모리 자동 정리
계약과 안전 문자열
- requires() / ensures() 매크로로 함수의 전·후 조건 명시
- 실패 시 명확한 오류 메시지 출력
- 실패 시 false 반환으로 안전한 오류 처리
동시성: 자동 잠금 해제와 스레드 매크로
- CLEANUP 기반 mutex 자동 해제 함수로 데드락 방지
- 스코프 종료 시 pthread_mutex_unlock() 자동 호출
- cgrep의 파일 처리 스레드 풀 구현에 사용
성능 최적화
- LIKELY() / UNLIKELY() 매크로로 핫패스 분기 예측 제공
- PGO 수준의 최적화 효과를 -O2 빌드에서도 확보
결론
- safe_c.h를 사용한 cgrep은 2,300줄의 C 코드로, 50회 이상의 수동 free() 호출을 제거
- 동일한 어셈블리와 실행 속도를 유지하면서 메모리 누수와 세그폴트 없는 안전한 C 코드 구현
- C의 단순함과 자유로움을 유지하면서 현대적 안전성을 결합한 사례
- 작성자는 이후 글에서 cgrep이 ripgrep보다 2배 이상 빠르고 메모리 사용량은 20배 적은 이유를 다룰 예정
- safe_c.h는 새 프로젝트에 적합, 매크로 기반이라 디버깅 난이도 상승 가능성 언급
- 다양한 정적 분석기(GCC analyzer, ASAN, UBSAN, Clang-tidy 등)로 정확성과 안전성 검증 수행