- 2023 में C code लिखने का मेरा तरीका काफ़ी बदल गया, और अब मेरी style मुख्यतः छोटे type names, null-terminated strings से परहेज़, struct return, और single translation unit compilation के इर्द-गिर्द दोबारा व्यवस्थित हुई है
u8,i32,size,s8जैसे छोटे alias, औरconstवstructको हटाना, बार-बार आने वाली declarations के visual noise और cognitive load को कम करने का चुनाव है- strings को null termination की जगह
dataऔरlenवालेs8fat pointer के रूप में संभाला जाता है, और Win32·UTF-16 environment मेंc16औरs16भी साथ इस्तेमाल होते हैं - function design में out parameters की बजाय struct return को प्राथमिकता दी जाती है, और zero-initialized return value में success के समय ही
okसेट करने वाला pattern इस्तेमाल होता है - macros, assert, Win32 declarations, और inline assembly तक, पढ़ने में आसान local rules को महत्व दिया जाता है, लेकिन दूसरे project में योगदान करते समय उसी project की style अपनाई जाती है
छोटे type names से intent स्पष्ट करना
- basic integer, character, और pointer-size types के लिए छोटे alias इस्तेमाल किए जाते हैं
- उदाहरण:
u8,c16,b32,i32,u32,u64,f32,f64,uptr,byte,size,usize
- उदाहरण:
- ये नाम पूरे program में बहुत बार आते हैं, इसलिए संक्षिप्तता का पढ़ने और review करने में सीधा लाभ है
_tsuffix अब इस्तेमाल नहीं किया जाता, क्योंकि अब यह दृश्य रूप से बिखराव पैदा करने वाला तत्व लगता है- signed type prefix के रूप में
sकी बजायiको पसंद किया जाता हैsको string type names के लिए खाली रखा जाता है
- size type के लिए
isizeकी बजायsizeइस्तेमाल होता है- क्योंकि signed size को अधिक महत्वपूर्ण default माना जाता है
usizeका उपयोग मुख्यतः external interface के साथ interaction जैसे सीमित मामलों में होता है
b32नाम “32-bit boolean” वाली intent को स्पष्ट करता है_Boolकी बजाय natural word size का उपयोग होता है- व्यवहार में यह अक्सर register में होता है या struct padding में चला जाता है
- जब memory सचमुच महत्वपूर्ण हो, तब boolean को
flagsvariable में compress किया जाता है
c16Win32 में आवश्यक UTF-16 characters के लिए type है- अगर यह
char16_tपर आधारित हो, तो GDB जैसे debugger उसे character data के रूप में दिखाने में मदद करते हैं - Win32 का official type name
wchar_tहै, लेकिन UTF-16 को स्पष्ट करना अधिक पसंद है
- अगर यह
u8का उपयोग octet और मुख्यतः UTF-8 data के लिए होता है, जबकिbyteको raw memory और विशेष aliasing type के रूप में अलग रखा जाता है- fixed-width types को support न करने वाले system की चिंता को व्यावहारिक रूप से कम महत्व दिया जाता है
int_fast32_tजैसे लंबे type names भी अनावश्यक बर्बादी लगते हैं
- जब केवल code snippet अलग से दिखाया जाता है, तब ये alias अकेले नहीं लिखे जाते
- क्योंकि reader को context समझने के लिए
typedefभी साथ चाहिए होता है
- क्योंकि reader को context समझने के लिए
macro और assert नियम
- function-like macros को lowercase में लिखा जाता है
- उदाहरण:
countof(a),lengthof(s),new(a, t, n)
- उदाहरण:
- constants के लिए अब भी
ALL_CAPSपसंद है, लेकिन function-like macros के लिए lowercase ज़्यादा readable लगता है - function-like macros में सामान्य macros की तुलना में namespace समस्या कम होती है
new()macro औरnewvariable/field एक साथ रखे जा सकते हैं- क्योंकि function-call form न होने पर macro expand नहीं होता
- GCC और Clang के लिए
assertmacro,while (!(c)) __builtin_unreachable()रूप का उपयोग करता है - इस assert तरीके में अलग build configuration बाँटने की ज़रूरत नहीं पड़ती
- debug और release build के लिए अलग definitions रखने की ज़रूरत नहीं होती
- व्यवहार Undefined Behavior Sanitizer यानी UBSan की मौजूदगी से नियंत्रित होता है
libubsanfile name और line number सहित diagnostic output देता है- release build में यह व्यावहारिक optimization hint बन जाता है
- release build में assertion चालू करने के लिए UBSan को
-fsanitize-trapके साथ trap mode में रखें और कम से कम-fsanitize=unreachableचालू करें - सैद्धांतिक रूप से
-funreachable-trapsसे भी यह संभव है, लेकिन लिखने के समय तक की कुछ हाल की GCC releases में यह टूटा हुआ है
declarations में क्या कम किया जाता है
- parameters पर
constनहीं लगाया जाता- इसे optimization में व्यावहारिक भूमिका वाला नहीं माना जाता
- ऐसा कोई उदाहरण याद नहीं आया जहाँ इससे गलती पकड़ी गई हो, या पकड़ी जा सकती थी
- अच्छे parameter names को prototype documentation के लिए पर्याप्त माना जाता है
constहटाना cognitive load और visual noise घटाकर productivity बढ़ाने वाला बदलाव रहा है- एक छोटा अपवाद यह है कि code के पास static tables को read-only memory में रखने के संकेत के रूप में
constअब भी पसंद है- ज़रूरत हो तो cast के जरिए
constहटाया जाता है
- ज़रूरत हो तो cast के जरिए
- null pointer के लिए literal
0इस्तेमाल होता है- यह लगभग 7 साल से इस्तेमाल की जा रही style है
- सैद्धांतिक खामी की संभावना मानी जाती है, लेकिन लाखों lines के code में इसका वास्तविक उदाहरण नहीं देखा गया
restrictकेवल ज़रूरत पड़ने पर इस्तेमाल होता है- code को इस तरह व्यवस्थित किया जाता है कि out parameters loop में न आएँ, या out parameters से ही बचा जाए
inlineका उपयोग नहीं किया जाता- क्योंकि सब कुछ एक ही translation unit में compile किया जाता है
- हर struct को
typedefकिया जाता हैstructkeyword हटाने से code अधिक readable होता है- recursive struct के लिए उसके ठीक ऊपर forward declaration दी जाती है, और fields में छोटे names उपयोग होते हैं
- entry point को छोड़कर हर function को
staticघोषित किया जाता है- क्योंकि single translation unit compilation को आधार माना गया है
- छोटे type names,
constहटाने, औरstructहटाने की वजह से function return type और function name को एक ही line पर आराम से रखा जा सकता है - type names को uppercase में लिखने का दौर भी था, लेकिन अंततः उसे छोड़ दिया गया
strings में null termination की जगह s8
- सबसे productive बदलावों में से एक यह रहा कि null-terminated strings को पूरी तरह छोड़कर
dataऔरlenवालेs8string type का उपयोग किया जाने लगा s8struct:u8 *datasize len
s8(s)macro, C string literal कोs8string में wrap करता हैs8को fat pointer की तरह value के रूप में pass और return किया जाता हैs8function prefix के रूप में भी अच्छा काम करता है- क्योंकि
strfamily के names reserved हैं - उदाहरण:
s8span,s8equals,s8compare,s8hash,s8trim,s8clone
- क्योंकि
- literal comparison के लिए
s8equals(tagname, s8("body"))जैसी form उपयोग होती है - flexible array member के साथ size और array को एक allocation में बाँधने का प्रयोग भी किया गया, लेकिन इसकी सीमित flexibility को फ़ायदों से बड़ा माना गया
- कभी-कभी लगा कि छोटे program में string type की ज़रूरत नहीं होगी, लेकिन अधिकतर मामलों में यह आकलन गलत निकला
- UTF-16 support के लिए
s16भी उपयोग होता है- इसमें
c16 *dataऔरsize lenहोते हैं - macro में literal पर
uprefix लगाने के तरीके को लेकर अभी पूरी तरह भरोसा नहीं है
- इसमें
struct return और initialization pattern
- out parameters की बजाय struct return को पसंद किया जाता है
- यह व्यावहारिक रूप से multiple values return करने का तरीका है, भले destructuring न हो
- उदाहरण
i32parse(s8)parsing resultvalueऔर statusokको साथ लौटाता है - अतिरिक्त copy cost को व्यवहारिक काम में बड़ा मुद्दा नहीं माना जाता
- calling convention इसे hidden
restrictout parameter में बदल सकती है - या inline हो जाने पर return-value overhead का महत्व नहीं रहता
- calling convention इसे hidden
- यह तरीका error दिखाने के लिए special null return जैसे in-band signal का लालच कम करता है
- function की शुरुआत में zero-initialized return value बनाकर हर
returnमें उसी का उपयोग करने वाला pattern पसंद किया जाता है- error पर तुरंत zero-initialized state में return होता है
- success path में return से ठीक पहले
okको true किया जाता है
- static data और
s8·s16macros को छोड़कर initializer का उपयोग भी कम किया जाता है- designated initializer से भी बचा जाता है, और assignment statements से initialization किया जाता है
- assignment-based initialization पढ़ने में आसान है, और हर assignment के बीच sequence point होने से स्पष्ट क्रम मिलता है
- random number generation जैसे initialization में, जहाँ call order result को प्रभावित कर सकता है, वहाँ संभावित value combinations के बारे में अलग से सोचना नहीं पड़ता
Win32 declarations और inline assembly
__attribute__की बजाय__attributeपसंद किया जाता है- आखिर का
__ज़रूरत से ज़्यादा और अनावश्यक लगता है
- आखिर का
- Win32 system programming में
windows.hको include नहीं किया जाता, बल्कि ज़रूरी prototypes सीधे लिखे जाते हैं- आम तौर पर ज़रूरी declarations और definitions की संख्या बहुत अधिक नहीं होती
- इससे build time घटता है और namespace भी कम बिखरता है
- यह
DWORD,BOOL,ULONG_PTRकी बजायu32,b32,uptrजैसे custom types के साथ अधिक साफ़ मेल खाता है
- Win32 declaration उदाहरण में
W32(r) __declspec(dllimport) r __stdcallmacro का उपयोग होता हैExitProcess,GetStdHandle,VirtualAlloc,WriteConsoleA,WriteConsoleWजैसे functions सीधे declare किए जाते हैं
- inline assembly में बाहरी parentheses को braces की तरह माना जाता है
ifकी तरह opening parenthesis से पहले space रखा जाता है- हर constraint line colon से शुरू होती है
- इस style को छोटे program में देखने के लिए
wordhist.cउदाहरण के रूप में दिया गया है - थोड़ा बड़ा उदाहरण mini programming language implementation
asmint.cहै
1 टिप्पणियां
Hacker News की रायें
ऐसा लगता है कि उन्होंने माना कि
#define sizeof(x) (size)sizeof(x)में बाहरी कोष्ठकों की जरूरत नहीं है, लेकिन इसमें एक बेहद छोटा अपवाद हैcasting की precedence multiplication से ज्यादा होती है, इसलिए
sizeof(x) * 3,(size)sizeof(x) * 3के रूप में सुरक्षित तरीके से काम करता हैलेकिन
(size)sizeof(x)[y]में array indexing casting से पहले लागू होती है, इसलिए यह((size)sizeof(x))[y]नहीं, बल्कि(size)(sizeof(x)[y])बन जाता हैअसली code में
sizeof(x)को index करने की नौबत नहीं आती, लेकिन C मेंinteger[pointer]कोpointer[integer]जैसा ही अर्थ रखने की अनुमति है, इसलिए कोष्ठकों की कमी के कारण यह macro compile होकर गलत व्यवहार कर सकता हैज्यादा मूल बात यह है कि signed size बेहतर है, इस दावे से भी सहमत होना मुश्किल है। लेखक कहते हैं कि unsigned size खामियों का स्रोत है, लेकिन दिए गए code में भी अगर
countnegative हो तो memory खराब करने वाला bug हैunsigned integer में negative count व्यक्त नहीं होता, और overflow होने पर भी वह बहुत बड़ा positive number बन जाता है, जो मौजूदा checks में पकड़ा जाता है। निजी तौर पर मैं unsigned integer इस्तेमाल करने, लेकिन जहां तक संभव हो range-check wrapper से overflow पर रोक देने को तरजीह देता हूं
_Boolकी semantics मुझे उलटे पसंद हैक्योंकि
if (flags & FLAG_ALLOCATED)में ठीक से काम करने वाली expression को_Bool need_free = flags & FLAG_ALLOCATED;की तरह boolean variable में निकाला जा सकता हैflags & FLAG_ALLOCATEDset होने पर 1 नहीं, बल्कि कोई भी non-zero value हो सकता है, और_Boolइसे 1 में normalize कर देता है।intमें लेने परif (need_free)pass हो जाता है, लेकिनif (need_free == true)fail हो सकता हैइसकी कमियां भी हैं। refactoring के दौरान अगर आप यह चूक जाएं कि
_Boolमें implicit conversion कोई उपयोगी काम कर रहा था, तोif ((flags & FLAG_ALLOCATED) == true)जैसा गलत code बन सकता हैसाथ ही disk से struct पढ़ते समय या arbitrary bytes भरते समय अगर
_Boolfield 0 या 1 नहीं है, तो undefined behavior का जोखिम होता है(size)(sizeof(x)[y])भी कई लोगों को चौंकाएगा, लेकिन यह(size)(sizeof ((x)[y]))के समान हैsizeoffunction नहीं, बल्कि unary operator है, और indexing तथा function call की precedencesizeofसे ज्यादा होती है। इसलिए मैंsizeofके बाद space रखने और जरूरत पड़ने पर ही operand पर parentheses लगाने का तरीका पसंद करता हूंhttps://en.cppreference.com/w/c/language/operator_precedence
macro को सही तरीके से लिखना हो तो यह
#define sizeof(x) ((size)(sizeof (x)))होगाअपने खुद के types define करना एक कदम ज्यादा आगे जाने जैसा लगता है
जो लोग पहले से C types से परिचित हैं, उन्हें भी किसी program को समझने के लिए एक अलग, अजीब system सीखना पड़ेगा। size को स्पष्ट करना जायज है, इसलिए
uintकी बजायuint32_tइस्तेमाल करने जैसी बात समझ आती हैऐसे types किसी उचित header में defined होने चाहिए, और हो सकता है मैं गलत होऊं क्योंकि मैंने लंबे समय से C इस्तेमाल नहीं किया है
int32-bit होता है16-bit target पर नहीं, लेकिन क्या आप सचमुच 5MB के program को 16-bit पर port करने वाले हैं? ऐसी चिंता आमतौर पर worthwhile नहीं होती
समस्या
longहै। कुछ machines पर यह 32-bit है, कुछ पर 64-bit, इसलिए भ्रम पैदा करता है। शुक्र है किlong longहमेशा 64-bit होता है, इसलिएlongको छोड़ देना चाहिएchar8-bit,short16-bit,int32-bit,long long64-bit — बस। C मेंintके size पर हमने अंतहीन समय बर्बाद किया हैC अक्सर इस्तेमाल करने वालों के लिए यहां दिए गए abbreviations परिचित हैं, और custom type system के लिहाज से यह काफी elegant है। Rust याद आता है
stdint.hमें मौजूद हैंकई projects को यह file मेहनत से दोबारा बनाते देखना हमेशा हैरान करता है
standard types को फिर से अपने नामों में translate करके इस्तेमाल करना readers के लिए झंझट है। पहले एक C++ project में collections, references और composite objects के लिए ढेर सारे
typedefइस्तेमाल करने पर पूछा था, तो जवाब मिला कि इससे समझना आसान होता हैबाद में मैंने उस व्यक्ति के monitor के पास typedef cheatsheet चिपकी हुई देखी
अक्सर
dim_tजैसे types दिखते हैं, जो use case के हिसाब से 32-bit या 64-bit हो जाते हैं। 64-bit platforms पर भी pointer compression structures में 32-bit integers अक्सर इस्तेमाल होते हैंउदाहरण के लिए, अगर आप खुद heap allocate करें और केवल 32-bit offsets store करें, तो 4GB से कम workloads में memory usage आधा हो जाता है, और cache locality भी बेहतर होकर performance सुधरती है
निजी पसंद के कारण C की स्थापित conventions को छोड़ देना थोड़ा ज़्यादा लगता है
uint8_tयाint32_tकी जगहu8,i32लिखने से कुछ अक्षर बचेंगे, लेकिन दूसरे लोग code पढ़ते समय उलझ सकते हैंnull-terminated string की जगह custom string type इस्तेमाल करना भी, इस बात को देखते हुए कि C ऐसी strings के इर्द-गिर्द बनी है, collaboration को कठिन बनाने जैसा लगता है
windows.hinclude किए बिना Win32 API prototypes सीधे लिखना compile time घटा सकता है, लेकिन यह अच्छी तरह बनी तेज़ highway छोड़कर जंगल के रास्ते जाने जैसा है। इनमें से काफ़ी कुछ ऐसे C code की बजाय निजी पसंद के करीब लगता है जिसे हर कोई आसानी से संभाल सकेu8याi32typing बचाने के लिए नहीं, बल्कि पढ़ते समय होने वाला संवेदी बोझ घटाने के लिए हैंverbosity बनाम conciseness की बहस में हमेशा आने वाला “keystrokes की संख्या” वाला तर्क काफ़ी flawed है। यह मानना गलत है कि conciseness सिर्फ़ तेज़ typing के लिए अच्छी है, और verbosity पढ़ने के लिए हमेशा बेहतर है
verbosity के reading में फायदे हैं, लेकिन conciseness के भी फायदे हैं, और कोई भी स्पष्ट winner नहीं है। ये बस अलग-अलग trade-offs हैं
u16जैसे नाम बहुत इस्तेमाल होते हैं, और programmer को confuse करने की संभावना कम हैअसली टूटने वाली जगह तब है जब दो अलग-अलग programs अपनी-अपनी तरह से
u16define करके header file में expose करें, और तीसरा program दोनों headers को एक साथ include करेnamespace वाले library types
libname_u32जैसे दिखने लगते हैं, और उस मुकाम परlibname_prefix की जगह सीधेuint32_tइस्तेमाल करने का मन करता हैu8याi32देखकर confused होने की संभावना ज़्यादा से ज़्यादा theoretical है, और कुछ हद तक straw man argument जैसी लगती हैउन्हें चिढ़ हो सकती है, लेकिन confusion नहीं होगा। Rich Hickey ने जैसा कहा था, पढ़ना सीखने से पहले हर चीज़ पढ़ने में कठिन होती है
कहा जाता है कि 32-bit boolean इस्तेमाल करना beginners को memory waste जैसा लग सकता है, तो शायद मैं भी beginner ही हूँ
8-bit bool से खराब न होने के कुछ cases सुने हैं, लेकिन सच में बेहतर होने वाले cases नहीं दिखते। अगर struct में adjacent booleans हों, या function का boolean variable register से बाहर होकर stack पर रखा जाए, तब भी memory waste होती है
कुछ bytes ही सही, लेकिन जानबूझकर pessimization क्यों करें, समझ नहीं आता। बड़ा size इस्तेमाल करके मिलता क्या है?
cycle-wise struct के आगे condition value थी और पीछे 512, 1024, 2048 sample values आते थे; एक junior ने space बचाने के लिए struct को pack कर दिया और condition value को 8-bit 1-byte बना दिया
उस improved code ने Intel chips पर throughput लगभग 10x घटा दिया, और SPARC RISC architecture पर BUS ERROR दिया
struct header pack करने से data array aligned नहीं रहा, और Intel को चुपचाप दो 32-bit words लाकर उन्हें जोड़ना पड़ा, जबकि SPARC ने unaligned data पर ठीक ही नाराज़गी दिखाई
अगर यह long-term file storage नहीं बल्कि throughput-critical pipeline computation है, तो data को “space saving” के हिसाब से pack करने के बजाय architecture alignment के हिसाब से रखना बेहतर हो सकता है
लगभग सभी compilers यह कर देते हैं, इसलिए “struct alignment/padding” खोजकर देख सकते हैं। अगर compiler वैसे भी खाली जगह छोड़ने वाला है, तो उस memory को खुद इस्तेमाल करना बेहतर है; नहीं तो performance छूट सकती है
और सटीक तौर पर, हर field ऐसे address पर होना चाहिए जो उसके अपने size या wordline size से divisible हो, और पूरी struct भी सबसे बड़े field size के multiple तक padded होनी चाहिए। व्यवहार में इसका मतलब आम तौर पर 32-bit alignment होता है
संदर्भ: http://www.catb.org/esr/structure-packing/
booltype इस्तेमाल करने पर, valuefalseवाली 0 याtrueवाली 1 न हो तो sanitizer warning दे देता हैstruct return और output parameters पर किए गए दावों से सहमत नहीं हूँ
यह error return कर सकने वाले functions को combine करना कहीं ज़्यादा कठिन बना देता है, और जगह-जगह types बढ़ जाते हैं। असल में लगभग हर function fail हो सकता है, इसलिए खासकर अगर out-of-memory तक handle करना हो तो predictable error return style ज़्यादा महत्वपूर्ण है
यह बहुत कठिन है और फायदा भी लगभग नहीं है। उस बिंदु पर programming style चुनने से बहुत अलग समस्याएँ पैदा हो जाती हैं
errnoऔर output parameter semantics मिल सकते थे, लेकिन C में ऐसा नहीं हैफिर भी C में optional values को compose करना हमेशा थोड़ा दर्दनाक रहा है। exceptions न इस्तेमाल करें तो languages में exceptions और monads दो बड़े विकल्प जैसे लगते हैं, लेकिन दोनों C में फिट नहीं बैठते और अधिकतर C programmers की philosophy से भी मेल नहीं खाते
simple one-to-one calls के लिए macros आज़मा सकते हैं, लेकिन उनकी limits हैं। C++ भले ही भयानक हो, फिर भी C++ optional को
if(foo(x,y, out1, out2) != WHATEVER_LIBRARY_OK) { ... }से इस्तेमाल करना ज़्यादा सुखद हैहर function call पर
if (thing(...)) goto failलगाने वाला pattern भी बहुत शानदार नहीं लगता, लेकिन Go वालों को शायद पसंद हैया फिर
thread_local mylibrary_errnoहै, जो library के अंदर वास्तव में सही तरीका हो सकता है और boundary पर enum return value में convert किया जा सकता है“signed sizes are the way” पर मुझे लगता है कि बस यहीं तक पढ़ना काफी था
signed size एक बेहद चौंकाने वाला abstraction leak है और मुसीबत को बुलावा देने वाला तरीका है
यह बात भी मानना मुश्किल है कि
constकी कोई व्यावहारिक भूमिका नहीं है और उसने कभी गलतियां नहीं पकड़ीं। लोग input buffer और output buffer को अक्सर गड़बड़ा देते हैं, औरconstइसे तुरंत सामने ला देता हैentry point के अलावा सभी functions को
staticरखने की सलाह भी debugging के समय variables या functions न मिल पाने पर लेखक को कोसने की नौबत ला सकती हैstruct return को प्राथमिकता देना गलती से stack pointer return कर के बड़ा security hole खोलने के लिए अनुकूल है। output buffer पास करने पर ownership semantics साफ हो जाते हैं
यह सलाह मुख्यतः 64-bit system code लिखने वालों के लिए किसी हद तक ठीक हो सकती है, लेकिन 32-bit embedded क्षेत्र में यह जल्दी परेशानी पैदा कर सकती है
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p14...
constपसंद नहीं है वे कभी संतुष्ट नहीं होंगे, इसलिए जहां जरूरत हो वहांconstइस्तेमाल करें, जितना जरूरी हो उतना propagate करें, और शिकायतों को नजरअंदाज करेंवे हटाएं तो फिर से डाल दें। हमेशा वही पक्ष पहले थकता है। 25 साल से यही करता आया हूं और अभी भी यहां हूं
statictools पर निर्भर कर सकता है। करीब 15 साल पहले defaultstaticऔर हर जगहsize_tइस्तेमाल करने पर गया था, और अभी तक कोई समस्या नहीं आईदिलचस्प है कि मेरा अनुभव दूसरी दिशा में गया
https://dlang.org/blog/2023/10/02/crafting-self-evident-code...
लेख D पर केंद्रित है, लेकिन principles C पर भी लागू होते हैं
condition expression को
doX()औरdoZ()के अंदर ले जाने वाला हिस्सा रोचक लगा। यह हमेशा सही है या नहीं, पता नहीं; यह इस पर निर्भर करता है कि abstraction कहां रखी गई है और code को लेकर mental model क्या हैउदाहरण के लिए
deleteRecords();,if let x = deadRecords() deleteRecords(x);से बेहतर नहीं है। दूसरा ज्यादा अस्त-व्यस्त दिखता है, लेकिन सामने ही यह दिखाने की value है कि यह delete नहीं, pruning हैfunction को समझदारी से
pruneDeadProjects()जैसा बदल दें तो ठीक है, लेकिन सिर्फ condition को function के अंदर ले जाना context को खतरनाक बना सकता है और leaking abstraction बन सकता हैसभी structs के लिए
typedefइस्तेमाल करना संक्षिप्तता में मदद करता है, इसलिए मैं इसके पक्ष में हूंमुझे लगता है
typedefउदारता से इस्तेमाल किया जा सकता है। हालांकि सिर्फ target को हीtypedefकरना चाहिए, pointer को नहीं। pointer चाहिए तो कभी भी(type *)लिखा जा सकता हैखासकर function pointer के मामले में, function pointer नहीं बल्कि function itself को typedef करना चाहिए। तब function declaration में भी उस typedef का इस्तेमाल कर parameter type checking मिल सकती है, और function signature बदलते समय सभी declarations ठीक करने की जरूरत नहीं रहती
ज्यादातर C codebases इसमें गलती करते हैं और function pointer को typedef करते हैं, और फिर भी उस pointer definition से मेल खाते function declarations हाथ से लिखने पड़ते हैं
struct को return type की तरह इस्तेमाल करने वाले तरीके से मैं अभी आश्वस्त नहीं हूं। numeric error code को return value के रूप में रखना, और बाकी return values को output parameters से लेना मुझे ज्यादा पसंद है
typedefइस्तेमाल कर सभी fields private वाली class की नकल करना, और simple data structures के लिएstructइस्तेमाल करना मुझे पसंद हैclass तक सिर्फ functions से access होना चाहिए, और struct को directly access किया जा सकना चाहिए
यह मोटे तौर पर C/POSIX standard convention के करीब है। उदाहरण के लिए
pthread_tऔरstruct statका फर्क यही हैtypedefन करने वाली बात से सहमत हूंSDL_net जैसी चीजें ठीक यही करती हैं, इसलिए पसंद नहीं हैं। असल में pointer होता है, लेकिन value type जैसा
typedefकर दिया जाता हैइरादा समझ आता है, लेकिन यह काफी असहज तरीका है
इस लेख की कई बातें समझ में आती हैं
Arm64 के लिए bare-metal OS लिखना शुरू किया है, और अभी शुरुआती चरण में हूं, लेकिन मिलती-जुलती चीजें कर रहा हूं। Pascal strings इस्तेमाल कर रहा हूं, और type names भी बदले हैं। बस
i8नहीं बल्किint8style हैमैंने जल्दी ही तय कर लिया कि real software port करने का इरादा नहीं है, इसलिए standard C library functions या conventions का पालन करने की जरूरत नहीं है। इससे ज्यादा आजादी से experiment कर सकता हूं
C इतनी पुरानी language है कि जिस दौर में हर byte कीमती था उसका बोझ function names तक में बचा हुआ है। उससे बाहर निकलना अच्छा लगता है, और इस लेख की बातें और कई छोटे naming changes काफी साफ-सुथरी व्यवस्था जैसे लगते हैं
typedef float f32;,typedef double f64;यह मान लेना किfloat32-बिट है औरdouble64-बिट, एक खतरनाक सहारा जैसा लगता हैOpenCV
float16_tको define करता है, CUDA half-precision floating point को implement करता है, और microcontroller अपनी-अपनी तरह से implement कर सकते हैंC++23 fixed-width floating-point types लाता है, लेकिन C में इसे enforce करने का तरीका मुझे नहीं पता। compile time पर data loss न हो रहा हो, यह check करने के लिए macro रखना बेहतर लगता है
कुल मिलाकर, जैसा दूसरों ने कहा है, संक्षिप्त न भी हो तो readability के लिए कुछ चीज़ों को default ही रहने देना बेहतर हो सकता है
[0] https://docs.opencv.org/4.x/df/dc9/classcv_1_1float16__t.htm...
[1] https://docs.nvidia.com/cuda/cuda-math-api/group__CUDA__MATH...
[2] https://en.cppreference.com/w/cpp/types/floating-point
_Floattype हैtypedef _Float32 f32;typedef _Float64 f64;https://gcc.gnu.org/onlinedocs/gcc/Floating-Types.html