- safe_c.h, C diline C++ ve Rust’ın güvenlik ile kullanım kolaylığı özelliklerini ekleyen 600 satırlık özel bir başlık dosyasıdır ve bellek sızıntısı olmayan thread-safe grep (
cgrep) uygulamasında kullanıldı
- RAII, akıllı işaretçiler, otomatik temizleme (
cleanup) özniteliği sayesinde manuel free() çağrıları olmadan kaynak yönetimini otomatikleştirir
- Vektörler, görünümler, Result tipi, sözleşme makroları ile buffer overflow, hata işleme ve önkoşul doğrulamasını güvenli şekilde gerçekleştirir
- Mutex otomatik serbest bırakma, thread spawn makroları, dal tahmini optimizasyonu ile eşzamanlılık ve performansı korurken güvenlik sağlar
- Sonuç olarak aynı performansla (
-O2 düzeyinde) sızıntısız ve segfault’suz C kodu yazmanın mümkün olduğunu gösterir
safe_c.h genel bakış
safe_c.h, C++ ve Rust özelliklerini C koduna taşıyan bir başlık dosyasıdır
- C23
[[cleanup]] özniteliğini desteklemeyen derleyicilerde bile (GCC 11, Clang 18 vb.) aynı RAII (otomatik temizleme) davranışını sunar
CLEANUP(func) makrosu ile fonksiyon sonunda kaynaklar otomatik serbest bırakılır
LIKELY() ve UNLIKELY() makroları ile hot path dal tahmini optimizasyonu sağlar
Bellek yönetimi: UniquePtr ve SharedPtr
- UniquePtr, scope bittiğinde otomatik olarak
free() çağıran tek sahipli akıllı işaretçidir
AUTO_UNIQUE_PTR() makrosu ile tanımlandığında, hata oluşsa veya erken dönüş olsa bile bellek otomatik serbest bırakılır
- SharedPtr, son referans bırakıldığında kaynağı otomatik yok eden otomatik referans sayımlı bir yapıdır
shared_ptr_init() ve shared_ptr_copy() ile referans artırma/azaltma otomatik işlenir
- Thread’ler arasında paylaşılan güvenli struct yönetiminde kullanılır
Buffer overflow önleme: Vector ve View
DEFINE_VECTOR_TYPE() makrosu ile tip güvenli, otomatik genişleyen vektörler oluşturulur
- Yeniden tahsis, kapasite yönetimi ve temizleme (
cleanup) otomatik yürütülür
AUTO_TYPED_VECTOR() ile tanımlandığında scope sonunda otomatik serbest bırakılır
- StringView ve Span, ayrı bir
malloc olmadan string ve dizi dilimlerini işleyen sahiplik almayan referans yapılarıdır
DEFINE_SPAN_TYPE() ile tipe özel Span tanımlanır
- Sınır kontrolleri içerdiği için güvenli dizi erişimi sağlar
Hata işleme: Result tipi ve RAII
- Result yapısı, Rust’taki
Result<T, E> benzeri başarı/başarısızlık ayrımlı dönüş tipidir
DEFINE_RESULT_TYPE() ile tipe özel sonuç yapıları oluşturulur
RESULT_IS_OK() ve RESULT_UNWRAP_ERROR() ile açık hata işleme sunar
CLEANUP özniteliği ile birlikte kullanıldığında fonksiyon sonunda kaynaklar otomatik serbest bırakılır
AUTO_MEMORY() makrosu, malloc ile ayrılan belleği otomatik temizler
Sözleşmeler ve güvenli string’ler
requires() / ensures() makroları ile fonksiyonların önkoşul ve sonkoşulları açıkça belirtilir
- Başarısızlık durumunda net hata mesajı yazdırılır
safe_strcpy(), buffer boyutu kontrolü içeren bir kopyalama fonksiyonudur ve overflow’u önler
- Başarısız olduğunda
false döndürerek güvenli hata işleme sağlar
Eşzamanlılık: otomatik kilit açma ve thread makroları
CLEANUP tabanlı otomatik mutex serbest bırakma fonksiyonu deadlock’u önler
- Scope bittiğinde
pthread_mutex_unlock() otomatik çağrılır
SPAWN_THREAD() ve JOIN_THREAD() makroları ile thread oluşturma ve join işlemleri basitleştirilir
cgrep içindeki dosya işleme thread pool uygulamasında kullanılır
Performans optimizasyonu
LIKELY() / UNLIKELY() makroları ile hot path dal tahmini sağlar
-O2 derlemelerinde bile PGO düzeyinde optimizasyon etkisi elde edilir
- Güvenlik özellikleri eklense de performans kaybı yoktur
Sonuç
safe_c.h kullanan cgrep, 2.300 satırlık C koduyla 50’den fazla manuel free() çağrısını ortadan kaldırır
- Aynı assembly ve çalışma hızını korurken bellek sızıntısı ve segfault olmayan güvenli C kodu ortaya koyar
- C’nin sadeliğini ve özgürlüğünü korurken modern güvenliği birleştiren bir örnek sunar
- Yazar, sonraki yazısında
cgrep’in neden ripgrep’ten 2 kattan fazla hızlı ve bellek kullanımında 20 kat daha verimli olduğunu ele almayı planlıyor
safe_c.h’nin yeni projeler için uygun olduğu, ancak makro tabanlı yapısı nedeniyle debug etmenin zorlaşabileceği belirtiliyor
- Çeşitli statik analiz araçlarıyla (GCC analyzer, ASAN, UBSAN, Clang-tidy vb.) doğruluk ve güvenlik doğrulaması yapılmış
1 yorum
Hacker News yorumları
Bu yazı, C'de güvenli soyutlamalar (safe abstraction) uygulanırken ortaya çıkan maliyet sorununu gösteriyor.
Paylaşımlı işaretçi uygulaması POSIX mutex kullandığı için (1) platformdan bağımsız değil ve (2) tek iş parçacığında bile mutex ek yükü ödetiyor.
Yani bu, bir
zero-cost abstractiondeğil.C++'ın
shared_ptr'ı da aynı soruna sahip, ancak Rust bunuRcveArcolmak üzere iki ayrı türle ayırarak çözüyor.shared_ptr, mutex değil atomic işlemler kullanır.Rust'taki
Arc'a benzer; blogdaki uygulama sadece verimsiz.Yine de C++'ta
Rckarşılığı bir tür olmadığı için, yalnızca basit bir referans sayımlı işaretçi istendiğinde bile hâlâ maliyet oluşur.glibcvelibstdc++ortamlarında pthreads ile linklenmezseshared_ptrthread-safe değildir.Çalışma anında pthread sembollerini bulup atomic veya non-atomic yolunu seçer.
Bence her zaman atomic kullanmak daha iyi olurdu.
Çapraz platform desteği çoğu durumda sadece "olsa iyi olur" seviyesinde.
Mutex ek yükü can sıkıcı ama modern CPU'larda katlanılabilir düzeyde.
Rust'ın harika olduğunu biliyorum ama C ekosistemi o kadar büyük ki onu tamamen değiştirmek zor.
Bu durumda mutex'in ne avantaj sağladığını pek anlayamıyorum.
Fil'in (aka pizlonator) yaptığı, C'yi bellek açısından güvenli hâle getiren FUGC adlı bir garbage collector projesi var.
Mevcut koda neredeyse hiç dokunmadan uygulanabiliyor ve C/C++'ı bellek güvenli diller hâline getiriyor.
ilgili HN gönderisine ve resmî siteye bakılabilir.
Bu yazı, bellek güvenliğinin özünü biraz yanlış ifade ediyor gibi görünüyor.
Yerel değişkenlerin otomatik serbest bırakılması ya da sınır denetimleri tek başına yeterli değil.
Asıl mesele, program genelindeki bellek ömrü yönetimi.
Örneğin
UniquePtrdöndürürken ya daSharedPtrkopyalarken referans sayımını unutup unutmadığınız veya intrusive list içindeki öğelerin ömrünü kimin yönettiği gibi konular.Sonuçta bu yaklaşım bana eski
#define xfree(p)kalıbından çok da farklı gelmiyor.UniquePtr, struct'ı değer olarak döndürebildiği için mümkün.Ama
SharedPtrkopyası referans sayımını artırmayı otomatik olarak yapmıyor.#define xfree(p)kalıbının neden kötü olduğunu merak ediyorum.C23'ün
[[cleanup]]özniteliğini getirdiği söylense de, gerçekte bu GCC uzantısı ve[[gnu::cleanup()]]olarak yazılmalı.örnek koda bakılabilir.
“C++: Bakın diğer diller gücümün küçük bir kısmını bile taklit etmek için ne kadar uğraşıyor” diye bir şaka vardı.
Makrolarla neden C++ taklit edilmeye çalışıldığını merak ediyorum ama yine de ilginç bir deneme.
Ama sonuçta C++17 özelliklerine kadar öykünülüyorsa, doğrudan C++ kullanmak daha iyi olmaz mı diye düşünüyorum.
C hâlâ çalışması kolay bir dil ama C++ o kadar karmaşık ki frontend olmadan yaklaşmak zor.
C++'a geçince build zinciri, name mangling,
libstdc++bağımlılığı gibi nedenlerle işler karmaşıklaşıyor.Buna karşılık C++'ı C tarzında kullanırsanız böyle bir kısıtlama olmaz.
setjmp/longjmptabanlı istisna işleme ile uyumlu değil.Bunun yerine POSIX'in
pthread_cleanup_pushyapısından esinlenen bir cleanup makro çifti ile entegre edilebilir.cleanup_push(fn, type, ptr, init)vecleanup_pop(ptr)kullanılarak yığın tabanlı temizleme rutinleri uygulanıyor.Bu yöntemin avantajı, denge hatalarını derleme zamanında yakalayabilmesi.
safeclibiçindeki gerçeksafec.hile karıştırmamak gerekir.safeclib header'ına bakılabilir.
Global constraint handler yüzünden tasarım hatası olarak değerlendiriliyor ve çoğu toolchain bunu desteklemiyor.
ilgili belgeye bakılabilir.
Nim dilini kullanırsanız
safe_c.h'nin sunduğu her şeyi elde edebilirsiniz.Nim, C'ye derlenir ve güvenlik ile performansı aynı anda sunar.
ARC tabanlı otomatik referans sayımı,
defer,Option[T], bounds-checking,likely/unlikelygibi çeşitli özellikleri varsayılan olarak sağlar.resmî site, ARC tanıtımı, view types, Option belgeleri, likely şablonu incelenebilir.
Bu yaklaşımın hedefi taşınabilirlik ise, gerçekte C99'da kalmak daha güvenli.
MSVC'nin C derleyicisi zorlayıcı olsa da çapraz platform için neredeyse vazgeçilmez.
Ben de benzer bir header yazdım ama taşınabilirlik sorunları yüzünden cleanup yardımcılarını eklemedim.
C kodu aynı zamanda C++ olarak da derlenebiliyorsa iyi çalışır.
Paket yöneticisi de beraberinde gelir.
Yazıda birkaç kez geçen cgrep için kod bağlantısı yok.
GitHub'da aynı isimde birçok proje var ama çoğu farklı dillerde yazılmış.
cgrep'ten bahsedildiğini bilmiyorum ve kendim denemek isterdim.