10 poin oleh GN⁺ 2025-11-19 | 1 komentar | Bagikan ke WhatsApp
  • safe_c.h adalah file header kustom sepanjang 600 baris yang menambahkan fitur keamanan dan kemudahan ala C++ dan Rust ke bahasa C, dan digunakan dalam implementasi grep yang aman terhadap thread tanpa kebocoran memori (cgrep)
  • Melalui RAII, smart pointer, dan atribut cleanup otomatis, pengelolaan resource diotomatisasi tanpa perlu pemanggilan free() manual
  • Dengan vector, view, tipe Result, dan makro kontrak, buffer overflow, penanganan error, dan verifikasi prasyarat dapat dilakukan dengan aman
  • Dengan pelepasan mutex otomatis, makro spawn thread, dan optimasi prediksi percabangan, keamanan tetap terjaga sambil mempertahankan konkurensi dan performa
  • Hasilnya, ini membuktikan kemungkinan menulis kode C tanpa kebocoran dan segfault dengan performa yang sama (setara tingkat -O2)

Ikhtisar safe_c.h

  • safe_c.h adalah file header yang membawa fitur C++ dan Rust ke dalam kode C
    • Bahkan pada compiler yang tidak mendukung atribut C23 [[cleanup]] (seperti GCC 11, Clang 18), ia tetap menyediakan perilaku RAII (pembersihan otomatis) yang sama
    • Makro CLEANUP(func) secara otomatis melepaskan resource saat fungsi berakhir
    • Makro LIKELY() dan UNLIKELY() untuk optimasi prediksi percabangan pada hot path

Manajemen memori: UniquePtr dan SharedPtr

  • UniquePtr adalah smart pointer kepemilikan tunggal yang otomatis memanggil free() saat scope berakhir
    • Jika dideklarasikan dengan makro AUTO_UNIQUE_PTR(), memori tetap dibebaskan otomatis meski terjadi error atau return lebih awal
  • SharedPtr adalah struktur dengan reference counting otomatis yang menghancurkan resource secara otomatis saat referensi terakhir dilepas
    • shared_ptr_init() dan shared_ptr_copy() menangani kenaikan/penurunan referensi secara otomatis
    • Digunakan untuk mengelola struct yang dibagikan secara aman antar-thread
    Iklan

Pencegahan buffer overflow: Vector dan View

  • Makro DEFINE_VECTOR_TYPE() membuat vector yang dapat berkembang otomatis dan type-safe
    • Realokasi, pengelolaan kapasitas, dan pembersihan ditangani secara otomatis
    • Jika dideklarasikan dengan AUTO_TYPED_VECTOR(), resource dibebaskan otomatis saat scope berakhir
  • StringView dan Span adalah struct referensi non-owning untuk menangani slice string dan array tanpa malloc tambahan
    • DEFINE_SPAN_TYPE() mendefinisikan Span per tipe
    • Dilengkapi pemeriksaan batas untuk menjamin akses array yang aman

Penanganan error: tipe Result dan RAII

  • Struct Result adalah tipe nilai balik yang membedakan sukses/gagal mirip Result<T, E> milik Rust
    • DEFINE_RESULT_TYPE() membuat struct hasil per tipe
    • RESULT_IS_OK() dan RESULT_UNWRAP_ERROR() memungkinkan penanganan error yang jelas
  • Dikombinasikan dengan atribut CLEANUP, resource dibebaskan otomatis saat fungsi selesai
    • Makro AUTO_MEMORY() membersihkan memori hasil malloc secara otomatis
    Iklan

Kontrak dan string aman

  • Makro requires() / ensures() menyatakan kondisi sebelum dan sesudah fungsi
    • Jika gagal, pesan error yang jelas akan ditampilkan
  • safe_strcpy() adalah fungsi copy dengan pemeriksaan ukuran buffer, untuk mencegah overflow
    • Jika gagal, fungsi mengembalikan false sehingga penanganan error tetap aman

Konkurensi: unlock otomatis dan makro thread

  • Fungsi pelepasan mutex otomatis berbasis CLEANUP mencegah deadlock
    • pthread_mutex_unlock() dipanggil otomatis saat scope berakhir
    Iklan
  • Makro SPAWN_THREAD() dan JOIN_THREAD() menyederhanakan pembuatan dan join thread
    • Digunakan dalam implementasi thread pool pemrosesan file pada cgrep

Optimasi performa

  • Makro LIKELY() / UNLIKELY() menyediakan prediksi percabangan untuk hot path
    • Efek optimasi setingkat PGO dapat diperoleh bahkan pada build -O2
  • Meski fitur keamanan ditambahkan, tidak ada penurunan performa

Kesimpulan

  • cgrep yang menggunakan safe_c.h terdiri dari 2.300 baris kode C, dan menghilangkan lebih dari 50 pemanggilan free() manual
  • Tetap mempertahankan assembly dan kecepatan eksekusi yang sama, sambil mewujudkan kode C yang aman tanpa kebocoran memori dan segfault
  • Ini adalah contoh yang menggabungkan keamanan modern sambil mempertahankan kesederhanaan dan kebebasan C
  • Penulis menyebutkan bahwa tulisan berikutnya akan membahas alasan cgrep bisa lebih dari 2 kali lebih cepat dari ripgrep dan menggunakan memori 20 kali lebih sedikit
  • safe_c.h disebut cocok untuk proyek baru, meski juga disinggung bahwa pendekatan berbasis makro dapat meningkatkan kesulitan debugging
  • Verifikasi akurasi dan keamanan dilakukan dengan berbagai static analyzer (GCC analyzer, ASAN, UBSAN, Clang-tidy, dll.)

1 komentar

 
GN⁺ 2025-11-19
Komentar Hacker News
  • Tulisan ini menunjukkan masalah biaya yang muncul saat mengimplementasikan abstraksi aman (safe abstraction) di C
    Implementasi shared pointer menggunakan POSIX mutex sehingga (1) tidak independen terhadap platform dan (2) tetap harus membayar overhead mutex bahkan dalam single-thread
    Artinya, ini bukan ‘zero-cost abstraction’
    shared_ptr di C++ juga memiliki masalah yang sama, tetapi Rust menyelesaikannya dengan membedakan dua tipe, Rc dan Arc

    • shared_ptr di C++ tidak memakai mutex melainkan operasi atomic
      Ini mirip dengan Arc di Rust, dan implementasi di blog itu hanya sekadar tidak efisien
      Namun, di C++ tidak ada tipe yang setara dengan Rc, jadi jika menginginkan pointer reference counting sederhana, biayanya tetap ada
    • Di lingkungan glibc dan libstdc++, jika tidak me-link pthreads maka shared_ptr tidak thread-safe
      Saat runtime, ia mencari simbol pthread lalu memilih jalur atomic atau non-atomic
      Menurut saya lebih baik selalu memakai atomic
    • Saya merasa jauh lebih penting untuk membuat kode tidak crash
      Cross-platform dalam kebanyakan kasus hanya sebatas ‘bagus kalau ada’
      Overhead mutex memang menjengkelkan, tetapi pada CPU modern masih berada di tingkat yang bisa ditanggung
      Saya tahu Rust itu hebat, tetapi ekosistem C terlalu besar sehingga sulit digantikan sepenuhnya
    • Alih-alih mutex, reference count juga bisa diimplementasikan dengan operasi atomic C11
      Dalam kasus ini, saya kurang paham apa keuntungan yang diberikan mutex
    • POSIX mutex sudah diimplementasikan di berbagai platform, jadi menurut saya justru ini adalah API yang lebih umum
  • Ada proyek untuk membuat C aman terhadap memori dengan garbage collector bernama FUGC buatan Fil (alias pizlonator)
    Ini bisa diterapkan ke kode yang sudah ada hampir tanpa perubahan, dan mengubah C/C++ menjadi bahasa yang aman terhadap memori
    Lihat postingan HN terkait dan situs resminya

    • Berkat ini saya jadi tahu proyek tersebut untuk pertama kalinya. Menurut saya ini percobaan yang sangat keren
    • Tetapi saya tidak ingin menanggung penurunan performa dari garbage collector
  • Tulisan ini tampaknya agak salah menggambarkan inti dari keamanan memori
    Pelepasan otomatis variabel lokal atau bounds checking saja tidak cukup
    Masalah sebenarnya adalah pengelolaan masa hidup memori di seluruh program
    Misalnya, apakah saat mengembalikan UniquePtr atau menyalin SharedPtr kita tidak lupa menaikkan reference count, atau siapa yang mengelola masa hidup elemen dalam intrusive list
    Pada akhirnya, pendekatan dalam tulisan ini terasa tidak jauh berbeda dari pola #define xfree(p) lama

    • UniquePtr memungkinkan karena struct bisa dikembalikan sebagai nilai
      Namun, penyalinan SharedPtr tidak secara otomatis menangani kenaikan reference count
    • Saya penasaran kenapa pola #define xfree(p) dianggap buruk
  • Katanya C23 memperkenalkan atribut [[cleanup]], tetapi pada praktiknya ini adalah ekstensi GCC dan harus ditulis sebagai [[gnu::cleanup()]]
    Lihat contoh kode

    • Sulit menemukan informasi terkait ini, tetapi pada akhirnya tampaknya yang berubah hanya sintaksnya, sementara fiturnya sendiri tetap merupakan ekstensi
  • Ada lelucon, “C++: lihat betapa kerasnya bahasa lain berusaha meniru sebagian dari kekuatanku”
    Saya penasaran kenapa orang mencoba meniru C++ dengan makro, tetapi bagaimanapun ini percobaan yang menarik

    • Menarik melihat proses membuat C yang lebih aman tanpa memasukkan semua fitur C++
      Namun ketika akhirnya sampai meniru fitur C++17, saya jadi berpikir bukankah lebih baik langsung memakai C++ saja
    • Saya ingin bahasa yang bisa di-parse
      C masih mudah ditangani, tetapi C++ terlalu rumit sehingga sulit didekati tanpa frontend
    • C itu sederhana sehingga enak untuk diutak-atik
      Begitu pindah ke C++, semuanya jadi lebih rumit karena build chain, name mangling, dependensi libstdc++, dan sebagainya
    • Proyek ini bisa memaksa sintaks yang dibatasi dengan hanya mengizinkan sebagian fitur C++
      Sebaliknya, jika memakai C++ dengan gaya C, pembatasan seperti itu tidak ada
    • Fakta bahwa vendor CPU embedded tidak menyediakan compiler C++ juga merupakan kendala yang realistis
  • Ini tidak kompatibel dengan exception handling berbasis setjmp/longjmp
    Sebagai gantinya, ini bisa diintegrasikan dengan pasangan makro cleanup yang terinspirasi dari pthread_cleanup_push milik POSIX
    Dengan cleanup_push(fn, type, ptr, init) dan cleanup_pop(ptr), rutin pembersihan berbasis stack dapat diimplementasikan
    Pendekatan ini punya kelebihan karena kesalahan keseimbangan bisa ditangkap saat compile time

  • Jangan sampai tertukar dengan safec.h asli milik safeclib
    Lihat header safeclib

    • Saya penasaran kenapa orang masih ingin memelihara implementasi Annex K
      Karena global constraint handler, ini dinilai sebagai kegagalan desain, dan sebagian besar toolchain tidak mendukungnya
      Lihat dokumen terkait
  • Jika memakai bahasa Nim, kita bisa mendapatkan semua yang disediakan safe_c.h
    Nim dikompilasi ke C, dan memberikan keamanan serta performa sekaligus
    Berbagai fitur seperti automatic reference counting berbasis ARC, defer, Option[T], bounds-checking, likely/unlikely, dan lainnya disediakan secara bawaan
    Lihat situs resmi, pengenalan ARC, view types, dokumentasi Option, dan template likely

  • Jika pendekatan ini menargetkan portabilitas, secara realistis paling aman tetap bertahan di C99
    Compiler C milik MSVC memang merepotkan, tetapi untuk cross-platform hampir wajib
    Saya juga pernah membuat header serupa, tetapi tidak memasukkan utilitas cleanup karena masalah portabilitas

    • Jika makro dibuat menghasilkan kode C++ berbasis destructor, ini bisa dilakukan tanpa atribut cleanup
      Jika kode C juga bisa dikompilasi sebagai C++, ini akan bekerja dengan baik
    • Bahkan di Windows, pengembangan dengan MSYS2 + GCC sudah cukup memadai
      Package manager juga disediakan
    • Sebagai catatan, MSVC sekarang mendukung C17
  • Tidak ada tautan ke kode cgrep yang beberapa kali disebut dalam tulisan
    Ada banyak proyek dengan nama yang sama di GitHub, tetapi kebanyakan ditulis dalam bahasa lain

    • Saya juga tidak tahu cgrep yang mana yang dimaksud, dan ingin mencobanya sendiri