वीकेंड प्रोजेक्ट: C भाषा के साथ एक मज़ेदार चुनौती
(lcamtuf.substack.com)- C सरल और अभिव्यक्तिपूर्ण है, लेकिन सिर्फ
switchस्टेटमेंट और लेबल नियमों से भी ऐसा कोड बनाया जा सकता है जो अनुभवी डेवलपर्स को भी चौंका दे - IOCCC-शैली का obfuscation मैक्रो और formatting से कोड को छिपाता है, लेकिन सिर्फ C के अपने grammar से भी पढ़ा जा सकने वाला, फिर भी अजनबी कोड संभव है
switch (...)में braces अनिवार्य नहीं हैं, और यह मिलते हुएcaseलेबल पर jump करता है, इसलिए यह सामान्य block initialization flow से अलग तरह से काम कर सकता हैcaseलेबल कोswitchblock के सबसे ऊपरी स्तर पर ही होना जरूरी नहीं है, इसलिएif (0) case...औरelse ifchain को मिलाकर braces के बिना switch भी compile हो जाता है- GNU C का
&&label-address extension custom switch या label-आधारित loop तक संभव बनाता है, लेकिन कुछ उदाहरण सिर्फ GCC-विशिष्ट हैं और undefined behavior के लिहाज़ से सुरक्षित नहीं हो सकते
C grammar से बनने वाला अजीब कोड
- C में कई कमियाँ हैं, लेकिन इसकी सरल syntax और अभिव्यक्तिपूर्ण प्रकृति की वजह से यह operating system जैसे बड़े software लिखने के लिए इस्तेमाल की जाती है
- इसकी संक्षिप्त syntax ने Java से Go तक कई mainstream बाद की भाषाओं की code structure पर असर डाला है
- code obfuscation का एक प्रतिनिधि उदाहरण IOCCC है
- IOCCC के विजेता code में अक्सर preprocessor macro, असामान्य formatting, बेकार variable name और उलझे हुए arithmetic expression होते हैं
- ऐसा code प्रभावित तो करता है, लेकिन क्योंकि उसे सामान्य code में reverse-engineer करना पड़ता है, इसलिए सीखने के लिहाज़ से वह उतना मज़ेदार नहीं भी हो सकता
switch, case, goto से दिखने वाले C के गहरे कोने
switch (...)कोif (...)याfor (...)की तरह braces के बिना लिखा जा सकता हैswitch (i) case 1: puts("i = 1");compile हो जाता है- braces न हों तो सिर्फ एक statement ही
switchसे जुड़ता है, इसलिए उसके बाद आने वालाcase 2:अबswitchके अंदर नहीं रहता और error बन जाता है
switchमूल रूप से मिलते हुएcaseलेबल पर जाने वालीgotoके काफ़ी करीब की संरचना हैswitchblock के अंदरint a = 123;औरputs(...)होने पर भी, अगर jumpdefault:पर होता है, तो उससे पहले का initialization code नहीं चलता- इस स्थिति में
aका initialization 123 से नहीं होता, और तकनीकी रूप से यह undefined behavior है - उदाहरण Godbolt पर देखा जा सकता है
caseलेबल का जुड़ा हुआswitchblock के सबसे ऊपरी स्तर पर होना जरूरी नहीं हैif (0) case 0: puts("i = 0");जैसी संरचना भी काम करती है- क्योंकि
switchसीधे उसcaseपर jump करता है, इसलिए उससे पहले काif (0)छोड़ दिया जाता है - उसके बाद
puts(...)के बाद आने वाले दूसरे output statement अब भीif (0)शर्त से रुके रहते हैं, इसलिएbreakके बिना भी fallthrough से बचा जा सकता है - उदाहरण Godbolt पर देखा जा सकता है
if ... elsechain व्याकरण की दृष्टि से एक ही top-level statement की तरह काम करती है, इसलिए इसेswitchके साथ जोड़कर braces के बिना अजीब switch बनाया जा सकता है- उदाहरण में
case 1 ... 10range case औरdefaultभी शामिल हैं - उदाहरण Godbolt पर देखा जा सकता है
- उदाहरण में
- GNU extension
&&operator से label का address लिया जा सकता है, और उस address परgotoकिया जा सकता है- इसका उपयोग करके
goto *(void*[]){ &&case_0, &&case_1, &&case_2 }[i];जैसी शैली में custom switch लागू किया जा सकता है - उदाहरण Godbolt पर देखा जा सकता है
- इसका उपयोग करके
- इसी GNU extension का उपयोग करके
for (...)के बिना भी variable declaration के अंदर label-आधारित loop लागू किया जा सकता है- उदाहरण में
i = 0सेi = 5तक print करने वाला loop labels औरgoto *&&_से बनाया गया है - उदाहरण Godbolt पर देखा जा सकता है
- यह आख़िरी snippet सिर्फ GCC के लिए है, और संभव है कि यह undefined behavior के लिहाज़ से सुरक्षित न हो
- उदाहरण में
- C में बिना जानबूझकर code को obfuscate किए भी पूरी तरह अजनबी और भ्रमित करने वाले रूप बनाए जा सकते हैं
1 टिप्पणियां
Hacker News टिप्पणियां
ऊपर वाले उदाहरण में कहा गया था कि
aकी वैल्यू प्रिंट होती है लेकिन वह 123 से initialize नहीं होती, पर C में असल में ऐसा हो भी सकता हैuninitialized variable का इस्तेमाल करने का मतलब “उस memory में संयोग से बची हुई वैल्यू” पढ़ना नहीं, बल्कि undefined behavior है, इसलिए compiler जो चाहे कर सकता है
उदाहरण के लिए वह उस memory को हमेशा 123 से initialize कर सकता है, या पूरे टुकड़े को undefined behavior मानकर सभी instructions हटा सकता है ताकि कुछ भी print न हो। इससे आगे, वह बाद वाला
returnया उससे पहले की instructions तक optimization में हटा सकता है, जिससे undefined behavior ऐसा लग सकता है जैसे वह “समय में पीछे जाकर” असर डाल रहा होयह पिछली instructions को प्रभावित कर सकता है, लेकिन code reordering या जटिल transformations undefined behavior के बिना भी होते हैं
static objects हमेशा initialized होते हैं, इसलिए ऐसी स्थिति पैदा नहीं होती
जो बचता है वह dynamic objects हैं, जैसे
mallocसे allocate किए गए struct के uninitialized members; लेकिन uninitialized dynamic memory पढ़ना C में undefined behavior नहीं है, बल्कि आपको uninitialized bits से बनने वाली वैल्यू मिलती है। अगर उस type में trap representation नहीं है, तो यह fail नहीं हो सकताइस तरह का syntax भी संभव है
switch(k) { if (0) case 0: x = 1; if (0) case 1: x = 2; if (0) default: x = 3; }इसे ऐसे
switchजैसा बनाया जा सकता है जिसमें हर clause के आखिर मेंbreakलिखने की जरूरत न हो, और#define brkcase if (0) caseजैसा macro भी संभव है। Compiler को control flow शायद पसंद न आए, लेकिन लगता है वह आम तौर पर इसे अच्छी तरह eliminate कर देगा#define brkcase break;caseभी शायद लगभग वैसे ही काम करे, लेकिन तब macro का मकसद थोड़ा खत्म हो जाता हैgotoसे लिखना बेहतर लगेगाcaselabel की body एक line की हो या braces में wrapped होपहले कभी मैंने यह structure इस अर्थ में इस्तेमाल किया था कि “अगले
caselabel की सिर्फ पहली line skip करो और बाकी में वैसे ही fall through हो जाओ”अगर
caselabels को statements के बीच separators नहीं, बल्कि सिर्फ labels माना जाए, तो सब समझ में आता हैC में coroutines implement करने के लिए इस technique का इस्तेमाल किया जा सकता है: https://stackoverflow.com/questions/24202890/switch-based-co...
पता नहीं मुझे क्यों नहीं पता था कि
case 1 ... 10:valid C हैमैंने C कई सालों तक इस्तेमाल की है, और सोच रहा हूं कि यह किस standard में आया था
extension का इतिहास नहीं मिला, और मेरी जानकारी में यह standard C में नहीं है।
clangको लेकर पक्का नहीं हूंपहले मजे के लिए मैंने 10 से 1 तक countdown करने वाला अजीब C code लिखा था
C, Python और shell versions सभी
&&और recursive calls का इस्तेमाल करके one-liner बनाए जा सकते हैंsys.stdout.write(f"{n}\n")कोprint(n)से बदला जा सकता हैमौजूदा code,
f-stringको छोड़ दें तो, Python 2 style जैसा दिखता है जहांprintstatement था। Python 3 मेंprintसामान्य function है औरNonereturn करता है जो false evaluate होता है, इसलिए पहलेandको भीorसे बदलना होगाएक और हैरान करने वाली बात यह है कि
4[arr],arr[4]के बराबर है*(array_label+offset)है, और यहां यह*(offset+array_label)बन जाता हैयानी
*(arr+4)और*(4+arr)एक ही हैंarr[i][j]औरj[i[arr]]भी बिल्कुल समान हैंबस यह जानना काफी है कि
a[x][y],(a[x])[y]जैसा है, औरa[x],x[a]जैसा हैarr[i][j]→(arr[i])[j]→(i[arr])[j]→j[i[arr]]लेख के आखिर का obfuscated code snippet एक और GCC extension दिखाता है: https://stackoverflow.com/questions/34559705/ternary-conditi...
इस blog author की ऐसी playful techniques मैंने पहले Twitter पर देखी थीं
switchstatement से loop भी बनाया जा सकता है: https://twitter.com/lcamtuf/status/1807129116980007037लगता है ऐसी
switchशरारतें Duff's device का अहम हिस्सा हैंswitchblock और loop को मिलाया जा सकता है,caseexplicitbreakन करने पर fall through करता है, और C में loop वापसswitchके अंदर jump कर सकता हैDuff memory-mapped I/O (MMIO) optimize करना चाहते थे, और आज की C में भी यह काम इस तरह नहीं किया जाएगा। अब MMIO, CPU instruction speed जितना तेज नहीं है, और data थोड़ा भी ज्यादा हो तो DMA इस्तेमाल किया जा सकता है
Modern languages शायद MMIO को सिर्फ pointer indirection की तरह treat भी नहीं करेंगी, लेकिन C ने इसे बनाए रखने के लिए type system में लगातार escape hatches जोड़े हैं
व्यक्तिगत तौर पर मुझे Tom Duff के “Device” की विरासत WUFFS का iterate loops mechanism लगता है। इसमें loop के N steps को आंशिक रूप से unroll करने का तरीका specify किया जाता है, और promise किया जाता है कि result main loop body को N बार चलाने जैसा ही होगा, लेकिन तेज हो सकता है। Vectorization के लिए intent समझना आसान हो जाता है, और
M % N != 0जैसे झंझट भरे edge cases को इंसान नहीं बल्कि tool सही तरीके से handle करता है