Hacker News 의견
  • 이 글은 C에서 안전한 추상화(safe abstraction) 를 구현할 때 생기는 비용 문제를 보여줌
    공유 포인터 구현이 POSIX mutex를 사용해서 (1) 플랫폼 독립적이지 않고 (2) 단일 스레드에서도 mutex 오버헤드를 지불하게 됨
    즉, ‘zero-cost abstraction’이 아님
    C++의 shared_ptr도 같은 문제를 가지지만, Rust는 Rc와 Arc 두 가지 타입으로 이를 구분해 해결함

    • C++의 shared_ptr은 mutex가 아니라 atomic 연산을 사용함
      Rust의 Arc와 유사하며, 블로그의 구현은 단순히 비효율적일 뿐임
      다만 C++에는 Rc에 해당하는 타입이 없어서, 단순 참조 카운팅 포인터를 원할 때는 여전히 비용이 발생함
    • glibc와 libstdc++ 환경에서는 pthreads를 링크하지 않으면 shared_ptr이 스레드 안전하지 않음
      런타임에서 pthread 심볼을 찾아 atomic 또는 비-atomic 경로를 선택함
      차라리 항상 atomic을 쓰는 편이 낫다고 생각함
    • 나는 코드가 크래시하지 않게 만드는 게 훨씬 중요하다고 느낌
      크로스플랫폼은 대부분의 경우 ‘있으면 좋은’ 수준임
      mutex 오버헤드는 짜증나지만, 현대 CPU에서는 감당 가능한 수준임
      Rust가 훌륭하다는 건 알지만, C 생태계가 너무 방대해서 완전히 대체하기는 어려움
    • mutex 대신 C11 atomic 연산으로 참조 카운트를 구현할 수도 있음
      이 경우 mutex가 주는 이점이 무엇인지 잘 모르겠음
    • POSIX mutex는 이미 여러 플랫폼에서 구현되어 있어서, 오히려 더 범용적인 API라고 생각함
  • Fil(aka pizlonator)이 만든 FUGC라는 가비지 컬렉터로 C를 메모리 안전하게 만드는 프로젝트가 있음
    기존 코드에 거의 수정 없이 적용 가능하며, C/C++을 메모리 안전 언어로 바꿔줌
    관련 HN 글공식 사이트 참고

    • 덕분에 이 프로젝트를 처음 알게 되었음. 정말 멋진 시도라고 생각함
    • 하지만 가비지 컬렉터의 성능 저하를 감수하고 싶지는 않음
  • 이 글은 메모리 안전성의 핵심을 다소 잘못 표현한 것 같음
    지역 변수 자동 해제나 경계 검사만으로는 충분하지 않음
    프로그램 전체의 메모리 수명 관리가 진짜 문제임
    예를 들어 UniquePtr을 반환하거나 SharedPtr을 복사할 때 참조 카운트를 잊지 않는지, intrusive list의 원소 수명은 누가 관리하는지 등
    결국 이 글의 접근은 예전의 #define xfree(p) 패턴과 크게 다르지 않다고 느낌

    • UniquePtr은 구조체를 값으로 반환할 수 있으니 가능함
      하지만 SharedPtr 복사는 참조 카운트 증가를 자동으로 처리하지 않음
    • #define xfree(p) 패턴이 왜 나쁜지 궁금함
  • C23이 [[cleanup]] 속성을 도입했다고 하지만, 실제로는 GCC 확장 기능이며 [[gnu::cleanup()]]로 써야 함
    예시 코드 참고

    • 관련 정보를 찾기 어렵던데, 결국 문법만 바뀐 것이고 기능 자체는 여전히 확장 기능인 듯함
  • “C++: 다른 언어들이 내 힘의 일부라도 흉내 내려면 얼마나 고생하는지 보라”는 농담이 있었음
    매크로로 C++을 흉내내는 이유가 궁금하지만, 어쨌든 흥미로운 시도임

    • C++의 모든 기능을 넣지 않고도 더 안전한 C를 만드는 과정이 흥미로웠음
      다만 결국 C++17의 기능까지 흉내내는 걸 보면, 그냥 C++을 쓰는 게 낫지 않을까 싶음
    • 나는 파싱 가능한 언어를 원함
      C는 여전히 다루기 쉽지만, C++은 너무 복잡해서 프론트엔드 없이는 접근이 어려움
    • C는 단순해서 해킹하기 좋은 언어
      C++로 넘어가면 빌드 체인, 네임 맹글링, libstdc++ 의존성 등으로 복잡해짐
    • 이 프로젝트는 C++의 일부 기능만 허용해 제한된 문법을 강제할 수 있음
      반면 C++을 C 스타일로 쓰면 그런 제약이 없음
    • 임베디드 CPU 벤더들이 C++ 컴파일러를 제공하지 않는 것도 현실적인 제약임
  • setjmp/longjmp 기반 예외 처리와는 호환되지 않음
    대신 POSIX의 pthread_cleanup_push에서 영감을 받은 cleanup 매크로 쌍으로 통합할 수 있음
    cleanup_push(fn, type, ptr, init)cleanup_pop(ptr)을 사용해 스택 기반 정리 루틴을 구현함
    이 방식은 컴파일 타임에 균형 오류를 잡아주는 장점이 있음

  • safeclib의 진짜 safec.h와 혼동하지 말아야 함
    safeclib 헤더 참고

    • Annex K 구현을 유지보수하려는 이유가 궁금함
      전역 constraint handler 때문에 설계 실패로 평가받고 있으며, 대부분의 툴체인이 지원하지 않음
      관련 문서 참고
  • Nim 언어를 쓰면 safe_c.h가 제공하는 기능을 모두 얻을 수 있음
    Nim은 C로 컴파일되며, 안전성과 성능을 동시에 제공함
    ARC 기반의 자동 참조 카운팅, defer, Option[T], bounds-checking, likely/unlikely 등 다양한 기능을 기본 제공함
    공식 사이트, ARC 소개, view types, Option 문서, likely 템플릿 참고

  • 이 접근법이 이식성을 목표로 한다면, 현실적으로는 C99에 머무르는 게 안전함
    MSVC의 C 컴파일러는 까다롭지만, 크로스플랫폼을 위해선 거의 필수임
    나도 비슷한 헤더를 만들었지만, 이식성 문제 때문에 cleanup 유틸리티는 넣지 않았음

    • 매크로로 C++ 코드(소멸자 기반)를 생성하게 하면 cleanup 속성 없이도 가능함
      C 코드가 C++로도 컴파일된다면 잘 동작함
    • Windows에서도 MSYS2 + GCC로 충분히 개발 가능함
      패키지 매니저도 함께 제공됨
    • 참고로 MSVC는 이제 C17을 지원함
  • 글에서 여러 번 언급된 cgrep의 코드 링크가 없음
    GitHub에 같은 이름의 프로젝트가 많지만 대부분 다른 언어로 작성되어 있음

    • 나도 어떤 cgrep을 말하는 건지 모르겠고, 직접 써보고 싶음