- safe_c.h एक 600-लाइन की कस्टम हेडर फ़ाइल है जो C भाषा में C++ और Rust की सुरक्षा व सुविधा सुविधाएँ जोड़ती है, और इसका उपयोग मेमोरी लीक-रहित thread-safe grep(cgrep) इम्प्लीमेंटेशन में किया गया है
- RAII, smart pointers, automatic cleanup
cleanup attribute के जरिए मैन्युअल free() कॉल के बिना resource management को ऑटोमेट किया जाता है
- vector, view, Result type, contract macros आदि की मदद से buffer overflow, error handling, और precondition validation को सुरक्षित तरीके से किया जाता है
- automatic mutex unlock, thread spawn macros, branch prediction optimization आदि के जरिए concurrency और performance बनाए रखते हुए safety सुनिश्चित की जाती है
- नतीजतन, समान performance (
-O2 स्तर) के साथ लीक और segfault के बिना C code लिखना संभव है यह दिखाया गया है
safe_c.h अवलोकन
- safe_c.h एक हेडर फ़ाइल है जो C++ और Rust की सुविधाओं को C code में लाती है
- C23 के
[[cleanup]] attribute को सपोर्ट न करने वाले compilers (GCC 11, Clang 18 आदि) में भी वही RAII (automatic cleanup) व्यवहार देता है
CLEANUP(func) macro से function समाप्त होने पर resources अपने-आप रिलीज़ हो जाते हैं
LIKELY() और UNLIKELY() macros के जरिए hot path branch prediction optimization
मेमोरी प्रबंधन: UniquePtr और SharedPtr
- UniquePtr एक single-ownership smart pointer है जो scope खत्म होने पर अपने-आप
free() कॉल करता है
AUTO_UNIQUE_PTR() macro के साथ declare करने पर error या early return की स्थिति में भी memory अपने-आप रिलीज़ हो जाती है
- SharedPtr एक automatic reference counting structure है जिसमें आख़िरी reference हटने पर resource अपने-आप destroy हो जाता है
shared_ptr_init() और shared_ptr_copy() से reference increment/decrement अपने-आप संभाला जाता है
- threads के बीच safely shared structs को manage करने में उपयोगी
buffer overflow रोकथाम: Vector और View
DEFINE_VECTOR_TYPE() macro से type-safe auto-growing vector बनाया जा सकता है
- reallocation, capacity management, और cleanup अपने-आप संभाले जाते हैं
AUTO_TYPED_VECTOR() के साथ declare करने पर scope खत्म होने पर auto free हो जाता है
- StringView और Span non-owning reference structures हैं, जो अलग
malloc के बिना string और array slices को संभालते हैं
DEFINE_SPAN_TYPE() से type-specific Span define किया जा सकता है
- boundary checks शामिल होने से safe array access सुनिश्चित होता है
error handling: Result type और RAII
- Result structure Rust के
Result<T, E> जैसा success/failure-discriminated return type है
DEFINE_RESULT_TYPE() से type-specific result structures बनाए जा सकते हैं
RESULT_IS_OK() और RESULT_UNWRAP_ERROR() से error handling स्पष्ट रहती है
CLEANUP attribute के साथ मिलाकर function समाप्ति पर resources auto-release किए जा सकते हैं
AUTO_MEMORY() macro से malloc की गई memory अपने-आप साफ़ होती है
contracts और safe strings
- requires() / ensures() macros से function की preconditions और postconditions स्पष्ट की जा सकती हैं
- failure होने पर साफ़ error message प्रिंट होता है
- safe_strcpy() एक buffer size check के साथ copy function है, जो overflow रोकता है
- failure पर
false return करके safe error handling देता है
concurrency: automatic unlock और thread macros
CLEANUP आधारित automatic mutex unlock function deadlock रोकने में मदद करता है
- scope समाप्त होने पर
pthread_mutex_unlock() अपने-आप कॉल होता है
SPAWN_THREAD() और JOIN_THREAD() macros से thread creation और join सरल हो जाते हैं
- cgrep के file-processing thread pool इम्प्लीमेंटेशन में उपयोग किया गया
performance optimization
LIKELY() / UNLIKELY() macros के जरिए hot path branch prediction मिलती है
-O2 build में भी PGO-स्तर जैसा optimization प्रभाव मिलता है
- safety features जुड़ने के बावजूद performance loss नहीं होता
निष्कर्ष
- safe_c.h का उपयोग करने वाला cgrep 2,300 lines of C code का है, और इसमें 50 से अधिक manual
free() calls हटाई गई हैं
- वही assembly और execution speed बनाए रखते हुए memory leak और segfault के बिना सुरक्षित C code इम्प्लीमेंट किया गया है
- C की सादगी और स्वतंत्रता बनाए रखते हुए modern safety जोड़ने का यह एक उदाहरण है
- लेखक अगली पोस्ट में बताएगा कि cgrep, ripgrep से 2x से अधिक तेज़ और memory usage में 20x कम क्यों है
- safe_c.h को नए projects के लिए उपयुक्त बताया गया है, लेकिन macro-आधारित होने के कारण debugging की कठिनाई बढ़ सकती है
- विभिन्न static analyzers (GCC analyzer, ASAN, UBSAN, Clang-tidy आदि) के जरिए correctness और safety verification किया गया है
1 टिप्पणियां
Hacker News टिप्पणियाँ
यह लेख C में safe abstraction लागू करते समय आने वाली लागत की समस्या दिखाता है
shared pointer का implementation POSIX mutex का उपयोग करता है, इसलिए (1) यह platform-independent नहीं है और (2) single-threaded स्थिति में भी mutex overhead देना पड़ता है
यानी यह ‘zero-cost abstraction’ नहीं है
C++ का
shared_ptrभी इसी समस्या से जूझता है, लेकिन Rust इसेRcऔरArcदो प्रकारों में बाँटकर हल करता हैshared_ptrmutex नहीं बल्कि atomic operations का उपयोग करता हैयह Rust के
Arcजैसा है, और ब्लॉग का implementation बस अक्षम हैहालांकि C++ में
Rcके बराबर कोई type नहीं है, इसलिए अगर केवल simple reference-counting pointer चाहिए तो तब भी लागत आती हैglibcऔरlibstdc++वातावरण में अगरpthreadslink नहीं किया गया हो तोshared_ptrthread-safe नहीं होताruntime पर
pthreadsymbols ढूँढ़कर atomic या non-atomic path चुना जाता हैमुझे तो लगता है कि हमेशा atomic का उपयोग करना बेहतर है
cross-platform support ज़्यादातर मामलों में बस ‘अच्छा हो तो ठीक’ जैसी चीज़ है
mutex overhead परेशान करता है, लेकिन modern CPU पर यह संभालने लायक है
Rust शानदार है, यह मैं मानता हूँ, लेकिन C ecosystem इतना विशाल है कि उसे पूरी तरह replace करना मुश्किल है
इस स्थिति में mutex के फायदे क्या हैं, यह मुझे स्पष्ट नहीं है
Fil (aka pizlonator) द्वारा बनाया गया FUGC नाम का एक garbage collector project है जो C को memory-safe बनाने की कोशिश करता है
इसे मौजूदा codebase में लगभग बिना बदलाव के लागू किया जा सकता है, और यह C/C++ को memory-safe language में बदल देता है
संबंधित HN पोस्ट और official site देखें
मुझे लगता है कि यह लेख memory safety के मूल मुद्दे को कुछ हद तक गलत ढंग से पेश करता है
local variables का automatic cleanup या bounds checking भर काफी नहीं है
पूरे program की memory lifetime management ही असली समस्या है
उदाहरण के लिए,
UniquePtrreturn करते समय याSharedPtrcopy करते समय reference count बढ़ाना भूल तो नहीं रहे, intrusive list के elements की lifetime कौन manage करेगा, आदिअंततः मुझे यह तरीका पुराने
#define xfree(p)pattern से बहुत अलग नहीं लगताUniquePtrसंभव है क्योंकि struct को value के रूप में return किया जा सकता हैलेकिन
SharedPtrcopy reference count बढ़ाने का काम अपने-आप नहीं करती#define xfree(p)pattern खराब क्यों माना जाता हैकहा गया है कि C23 ने
[[cleanup]]attribute पेश किया है, लेकिन वास्तव में यह GCC extension है और इसे[[gnu::cleanup()]]के रूप में लिखना पड़ता हैexample code देखें
एक मज़ाक था: “C++: देखो, दूसरी भाषाओं को मेरी शक्ति का थोड़ा-सा हिस्सा नकल करने के लिए भी कितनी मेहनत करनी पड़ती है”
macro से C++ की नकल क्यों की जा रही है, यह समझ नहीं आता, लेकिन फिर भी यह एक दिलचस्प प्रयास है
लेकिन आखिर में जब C++17 की सुविधाओं तक की नकल की जा रही है, तो लगता है कि सीधे C++ का उपयोग करना बेहतर नहीं होगा?
C अब भी संभालना आसान है, लेकिन C++ इतना जटिल है कि frontend के बिना उसके पास जाना मुश्किल है
C++ पर जाने के बाद build chain, name mangling,
libstdc++dependency जैसी चीज़ों से जटिलता बढ़ जाती हैजबकि C++ को C style में लिखने पर ऐसी पाबंदियाँ नहीं लगतीं
यह
setjmp/longjmpआधारित exception handling के साथ compatible नहीं हैइसके बजाय POSIX के
pthread_cleanup_pushसे प्रेरित cleanup macro pair के साथ इसे integrate किया जा सकता हैcleanup_push(fn, type, ptr, init)औरcleanup_pop(ptr)का उपयोग करके stack-based cleanup routine implement की जाती हैइस तरीके का फायदा यह है कि यह compile time पर balancing errors पकड़ सकता है
इसे
safeclibके असलीsafec.hके साथ भ्रमित नहीं करना चाहिएsafeclib header देखें
global constraint handler की वजह से इसे design failure माना जाता है, और अधिकतर toolchains इसका समर्थन भी नहीं करते
संबंधित दस्तावेज़ देखें
अगर आप Nim language का उपयोग करें, तो
safe_c.hजो सुविधाएँ देता है वे सब मिल सकती हैंNim, C में compile होती है और safety और performance दोनों देती है
ARC-आधारित automatic reference counting,
defer,Option[T], bounds-checking,likely/unlikelyजैसी कई सुविधाएँ यह default रूप से देती हैofficial site, ARC परिचय, view types, Option docs, likely template देखें
अगर इस approach का लक्ष्य portability है, तो व्यवहारिक रूप से C99 पर टिके रहना ही सुरक्षित है
MSVC का C compiler मुश्किल है, लेकिन cross-platform support के लिए लगभग अनिवार्य है
मैंने भी ऐसा ही एक header बनाया था, लेकिन portability issues की वजह से cleanup utilities शामिल नहीं कीं
अगर C code, C++ में भी compile हो सके तो यह अच्छी तरह काम करेगा
package manager भी साथ में मिलता है
लेख में कई बार आए cgrep के code का link नहीं दिया गया है
GitHub पर इसी नाम के कई projects हैं, लेकिन ज़्यादातर दूसरी languages में लिखे गए हैं
cgrepकी बात हो रही है, और मैं इसे खुद आज़माना चाहूँगा