C भाषा में अपरिभाषित व्यवहार को कम करना
C और C++ में undefined behavior—ऐसे operations जिनके लिए भाषा कोई गारंटी नहीं देती, जैसे signed integer overflow या null को dereference करना—bugs, security vulnerabilities, और आश्चर्यजनक compiler optimizations का एक केंद्रीय स्रोत बना हुआ है। टिप्पणीकार UB के ऐतिहासिक कारणों और aggressive optimization की आवश्यकता को, अधिक मामलों को implementation-defined behavior में बदलने, UB को trap या sanitize करने के opt-in modes जोड़ने, और C2y में “UB zoo” को छोटा करने के नए काम के पक्ष में दलीलों के साथ तौलते हैं। कई लोग मानते हैं कि वास्तव में मजबूत सुरक्षा Rust जैसी शक्तिशाली भाषाओं या CHERI और Fil-C जैसे hardware/ABI-level mechanisms से आएगी, जबकि C को low-level systems code तक सीमित रहना चाहिए जहाँ developers इन trade-offs को consciously स्वीकार करते हैं.
C में अपरिभाषित व्यवहार (UB) का दायरा और प्रकृति
- UB को “वह जो तब होता है जब आप एक अनिवार्य पूर्वशर्त का उल्लंघन करते हैं” के रूप में वर्णित किया गया है, जिससे कंपाइलरों को यह मानने की स्वतंत्रता मिलती है कि ऐसे मामले कभी होते ही नहीं।
- कई टिप्पणियाँ तर्क देती हैं कि इससे पूरे प्रोग्राम पर तर्क करना बेहद कठिन हो जाता है, क्योंकि कंपाइलर “UB कभी नहीं होता” के आधार पर ऑप्टिमाइज़ कर सकता है और UB बिंदु से पहले ही व्यवहार बदल सकता है।
- अन्य लोग नोट करते हैं कि C/C++ में UB कई अन्य भाषाओं की तुलना में असामान्य रूप से आक्रामक है, और यह प्रभावी रूप से “जो हार्डवेयर करता है” तक सीमित रहने के बजाय मनमाना व्यवहार (“nasal demons”) की अनुमति देता है।
ऑप्टिमाइज़ेशन बनाम शुद्धता
- UB के समर्थक कहते हैं कि यह ऑप्टिमाइज़ेशन के लिए आवश्यक है: कोई UB न होने की धारणा inlining, constant propagation, dead-code elimination, register allocation, और redundant checks को हटाने को सक्षम बनाती है।
- आलोचक जवाब देते हैं कि कंपाइलर अब UB को “weaponize” करते हैं, जिससे null checks या branches का आश्चर्यजनक रूप से हटना होता है और debugging कठिन हो जाती है।
- कुछ का सुझाव है कि कंपाइलरों को कम से कम तब चेतावनी देनी चाहिए जब UB-आधारित तर्क code को हटा दे, जबकि अन्य का कहना है कि यह अव्यावहारिक है क्योंकि लगभग सभी optimizations no UB मानने पर निर्भर करती हैं।
Implementation-defined बनाम undefined
- कई टिप्पणीकार तर्क देते हैं कि कई व्यवहार (जैसे null dereference, uninitialized reads) को पूरी तरह undefined होने के बजाय implementation-defined होना चाहिए या trap की गारंटी देनी चाहिए।
- विरोधी दृष्टिकोण: exotic hardware, WASM, और performance constraints पूरी तरह defined behavior को अव्यावहारिक बनाते हैं; C समितियाँ मौजूदा implementations को तोड़ने के लिए अनिच्छुक हैं।
Integer overflow
- इस पर तीखी बहस है कि signed overflow को defined होना चाहिए या नहीं (जैसे two’s-complement wrap, traps, या configurable)।
- परिभाषा के पक्ष में: runtime checks आसान होंगे, UB का दुरुपयोग कम होगा, व्यवहार सुसंगत रहेगा;
-fwrapv/-ftrapvजैसे flags पहले से मौजूद हैं। - परिभाषा के विरुद्ध: UB कंपाइलरों को स्पष्ट गलतियों पर चेतावनी देने, sanitizers का उपयोग करने, और शक्तिशाली optimizations बनाए रखने देता है; unsigned overflow पहले से defined है, फिर भी bug-prone है।
Uninitialized memory
- उदाहरण code दिखाता है कि कंपाइलर uninitialized malloc’d memory से reads का उपयोग आश्चर्यजनक तरीकों से optimize करने के लिए करते हैं (जैसे constant folding करके हमेशा एक ही branch लेना)।
- कुछ लोग “indeterminate लेकिन stable” values या implementation-defined semantics चाहते हैं; अन्य कहते हैं कि C2y ऐसा नहीं करेगा क्योंकि इससे बहुत सी implementations टूट जाएँगी।
विकल्प और mitigation
- सुझावों में शामिल हैं: applications के लिए memory-safe भाषाएँ (Rust, Go) उपयोग करें; low-level kernels के लिए C रखें; sanitizers (UBSan, ASan, TSan), Valgrind, और static analyzers पर भरोसा करें, उनकी सीमाओं के बावजूद।
- CHERI और Fil-C को ABI-level approaches के रूप में उद्धृत किया गया है जो memory behavior को कड़ाई से सीमित करते हैं और exploitability कम करते हैं, हालांकि compatibility और deployment में tradeoffs हैं।
मानक का विकास और layering
- बताया गया है कि C2y कुछ UB instances हटाता है, लेकिन optimization और portability के लिए कई अभी भी बने हुए हैं।
- इस पर चर्चा है कि safety के लिए जटिलता का बोझ किस layer (language, OS, ABI, hardware) पर होना चाहिए, लेकिन कोई स्पष्ट सहमति नहीं है।