2012-04-25 24 views
17

पर एक असामान्य तर्क को समझना एक कॉलेज प्रोग्रामिंग प्रतियोगिता में निम्नलिखित प्रश्न दिया गया था। हमें आउटपुट अनुमान लगाने और/या इसके कामकाज की व्याख्या करने के लिए कहा गया था। कहने की जरूरत नहीं है, हम में से कोई भी सफल नहीं हुआ।मुख्य

main(_){write(read(0,&_,1)&&main());} 

कुछ कम Googling मुझे इस सटीक सवाल का नेतृत्व किया, codegolf.stackexchange.com में पूछा:

https://codegolf.stackexchange.com/a/1336/4085

वहाँ, इसकी व्याख्या की क्या यह करता है: Reverse stdin and place on stdout, लेकिन नहीं कैसे

मैं भी इस सवाल में कुछ मदद मिली: Three arguments to main, and other obfuscating tricks लेकिन यह अभी भी कैसे main(_), &_ और &&main() काम करता है की व्याख्या नहीं करता।

मेरा प्रश्न है, कैसे ये वाक्यविन्यास काम करते हैं? क्या वे मुझे कुछ पता होना चाहिए, जैसा कि वे अभी भी प्रासंगिक हैं?

मैं किसी भी पॉइंटर्स (संसाधन लिंक इत्यादि) के लिए आभारी नहीं हूं, अगर सही जवाब नहीं है।

+0

वह प्रोग्राम C++ में संकलित नहीं होगा। सी ++ टैग को हटा रहा है। –

+0

@ रोब आह धन्यवाद। मै लापरवाह था। – RaunakS

+10

सी में भी, वह कार्यक्रम अपरिभाषित व्यवहार को कई तरीकों से आमंत्रित करता है। नतीजा केवल विशिष्ट प्रकार के सीपीयू को लक्षित करने वाले विशिष्ट कंपाइलरों के लिए अनुमानित है (यहां तक ​​कि कोडगोल्फ पर, यह प्रोग्राम केवल एक विशिष्ट अनुकूलन स्तर पर कुछ दिलचस्प करता है)। "यह प्रोग्राम क्या करता है" के सही जवाब? इसमें शामिल है "यह निर्भर करता है," "चाहे जो चाहें," और "यह आपको निकाल दिया जाता है।" –

उत्तर

26

यह प्रोग्राम क्या करता है? आपको पता होना चाहिए कि _, एक वैध चर नाम है एक बदसूरत यद्यपि

main(_) { 
    write (read(0, &_, 1) && main()); 
} 

पहले,:

main(_){write(read(0,&_,1)&&main());} 

इससे पहले कि हम यह विश्लेषण, चलो यह सुंदर बनाना हैं।के लिए इसे बदल डालते हैं:

main(argc) { 
    write(read(0, &argc, 1) && main()); 
} 

इसके बाद, एहसास है कि एक समारोह की वापसी प्रकार, और एक पैरामीटर के प्रकार सी में वैकल्पिक हैं (लेकिन C++):

int main(int argc) { 
    write(read(0, &argc, 1) && main()); 
} 

इसके बाद, समझ कैसे वापसी मूल्य काम करते हैं। कुछ CPU प्रकारों के लिए, वापसी मान हमेशा एक ही रजिस्टरों में संग्रहीत होता है (उदाहरण के लिए x86 पर EAX)। इस प्रकार, यदि आप return कथन छोड़ देते हैं, तो वापसी मूल्य जो भी सबसे हालिया फ़ंक्शन लौटाया जा रहा है।

int main(int argc) { 
    int result = write(read(0, &argc, 1) && main()); 
    return result; 
} 

read करने के लिए कॉल अधिक या कम स्पष्ट है: यह, &argc पर स्थित स्मृति में, (फ़ाइल वर्णनकर्ता 0) में मानक से पढ़ता 1 बाइट के लिए। यदि पढ़ने सफल हुआ, और 0 अन्यथा यह 1 देता है।

&& तार्किक "और" ऑपरेटर है। यह दाएं हाथ की तरफ का मूल्यांकन करता है अगर केवल बाएं हाथ की तरफ "सत्य" है (तकनीकी रूप से, कोई गैर-शून्य मान)। && अभिव्यक्ति का परिणाम int है जो हमेशा 1 ("सत्य" के लिए) या 0 (झूठी के लिए) होता है।

इस मामले में, दाईं ओर हाथ main कोई तर्क के साथ आमंत्रित करता है। main को कॉल करके 1 तर्क के साथ घोषित करने के बाद कोई तर्क नहीं है अपरिभाषित व्यवहार है। फिर भी, यह अक्सर काम करता है, जब तक आप argc पैरामीटर के प्रारंभिक मूल्य की परवाह नहीं करते हैं।

&& का परिणाम write() पर दिया गया है। तो, हमारा कोड अब इस तरह दिखता है:

int main(int argc) { 
    int read_result = read(0, &argc, 1) && main(); 
    int result = write(read_result); 
    return result; 
} 

हम्म। मैन पेजों पर एक त्वरित रूप से पता चलता है कि write तीन तर्क लेता है, एक नहीं। अपरिभाषित व्यवहार का एक और मामला। बहुत कम तर्कों के साथ main पर कॉल करने की तरह, हम भविष्यवाणी नहीं कर सकते कि write इसके दूसरे और तीसरे तर्कों के लिए प्राप्त होगा। ठेठ कंप्यूटरों पर, उन्हें कुछ मिलेगा, लेकिन हम निश्चित रूप से यह नहीं जान सकते कि क्या। (अटूट कंप्यूटर पर, अजीब चीजें हो सकती हैं।) लेखक write पर भरोसा कर रहा है जो पहले मेमोरी स्टैक पर संग्रहीत किया गया था। और, वह पर निर्भर है कि पढ़ने के लिए दूसरे और तीसरे तर्क हैं।

int main(int argc) { 
    int read_result = read(0, &argc, 1) && main(); 
    int result = write(read_result, &argc, 1); 
    return result; 
} 

main को अवैध कॉल फिक्सिंग, और हेडर जोड़ने, और && के विस्तार हमने:

#include <unistd.h> 
int main(int argc, int argv) { 
    int result; 
    result = read(0, &argc, 1); 
    if(result) result = main(argc, argv); 
    result = write(result, &argc, 1); 
    return result; 
} 


निष्कर्ष

इस कार्यक्रम के रूप में पर उम्मीद से काम नहीं चलेगा कई कंप्यूटर यहां तक ​​कि यदि आप मूल कंप्यूटर के समान कंप्यूटर का उपयोग करते हैं, तो यह एक अलग ऑपरेटिंग सिस्टम पर काम नहीं कर सकता है। यहां तक ​​कि यदि आप एक ही कंप्यूटर और एक ही ऑपरेटिंग सिस्टम का उपयोग करते हैं, तो यह कई कंपाइलरों पर काम नहीं करेगा। भले ही आप एक ही कंप्यूटर कंपाइलर और ऑपरेटिंग सिस्टम का उपयोग करते हैं, यदि आप कंपाइलर की कमांड लाइन झंडे को बदलते हैं तो यह काम नहीं कर सकता है।

जैसा कि मैंने टिप्पणियों में कहा था, प्रश्न का वैध जवाब नहीं है।यदि आपको एक प्रतियोगी आयोजक या प्रतियोगिता न्यायाधीश मिलता है जो अन्यथा कहता है, तो उन्हें अपने अगले प्रतियोगिता में आमंत्रित न करें।

+1

ओह वाह, वह बहुत था, _very_ व्यापक। एक स्पष्टीकरण: 'लिखना() 'वाक्यविन्यास' int लिखना है (int fd, char * Buff, int numbytes)'। तो मानक पढ़ने के लिए 'पढ़ने() 'का वापसी मूल्य' 1' बन रहा है? – RaunakS

+1

0 मानक इनपुट है, 1 मानक आउटपुट है, 2 मानक गलती है। इसलिए, पढ़ने से सफल वापसी (रिकर्सिव कॉल से मुख्य में सफल वापसी के साथ संयुक्त) stdout को लिखता है। Stdin को लिखने में पढ़ने के परिणामों से एक असफल वापसी। जो अभी तक एक और अपरिभाषित व्यवहार है। –

+0

आह हाँ, मुझे पूछने से पहले विकी करना चाहिए था। यह कोड आईओसीसीसी दावेदार बहुत अच्छा होगा। और यह अपरिभाषित व्यवहार है जैसे कि यह प्रतिकृति? मेरा मतलब है, एक ही कंपाइलर (जीसीसी 4.4.1) पर, क्या यह हमेशा एक ही परिणाम देगा? – RaunakS

8

ठीक है, _ एक डिफ़ॉल्ट प्रकार के int के साथ प्रारंभिक K & आर सी वाक्यविन्यास में घोषित एक चर है। यह अस्थायी भंडारण के रूप में कार्य करता है।

कार्यक्रम मानक इनपुट से एक बाइट पढ़ने की कोशिश करेगा। यदि इनपुट है, तो यह मुख्य रूप से एक बाइट पढ़ने के लिए जारी रखेगा।

इनपुट के अंत में, read(2) 0 वापस आ जाएगा, अभिव्यक्ति 0 वापस आ जाएगी, write(2) सिस्टम कॉल निष्पादित हो जाएगी, और कॉल श्रृंखला शायद अनदेखी होगी।

मैं "शायद" यहां कहता हूं क्योंकि परिणाम पर इस बिंदु से अत्यधिक कार्यान्वयन-निर्भर हैं। write(2) के अन्य पैरामीटर गायब हैं, लेकिन कुछ रजिस्टरों और स्टैक पर होंगे, इसलिए कुछ कर्नेल में पास हो जाएगा। वही अपरिभाषित व्यवहारmain के विभिन्न पुनरावर्ती सक्रियणों से वापसी मूल्य पर लागू होता है।

मेरे x86_64 मैक पर, प्रोग्राम ईओएफ तक मानक इनपुट पढ़ता है और फिर बाहर निकलता है, कुछ भी नहीं लिखता है।

+0

'_' क्या है पर कोई उद्धरण? इसके बारे में जानने के लिए उत्सुक –

+0

यह सिर्फ औपचारिक पैरामीटर * ("चर") * नाम है। यह 'मुख्य (int _)' के बराबर है ... कल्पना करें कि उन्होंने इसे "argc" * कहा है और यह सब स्पष्ट हो जाएगा। यह है: 'मुख्य (argc)' प्रारंभिक सी होगा डिफ़ॉल्ट ** int, ** * प्रोटोटाइप * घोषणाओं को बाद में जोड़ा गया था। वे सामान्य * argv * घोषित नहीं करते हैं लेकिन परिणामस्वरूप कठोर कुछ भी नहीं होगा। – DigitalRoss

+0

हाँ, एक सादा '_' एक कानूनी चर नाम है। –