2009-10-01 13 views
9

मैं एक परिचय सी ++ कक्षा के लिए एक टीए हूं। निम्नलिखित प्रश्न पिछले सप्ताह एक परीक्षण पर कहा गया था:सी ++ एम्बेडेड फ़ंक्शन कॉल के साथ आउटपुट मूल्यांकन ऑर्डर

int myFunc(int &x) { 
    int temp = x * x * x; 
    x += 1; 
    return temp; 
} 

int main() { 
    int x = 2; 
    cout << myFunc(x) << endl << myFunc(x) << endl << myFunc(x) << endl; 
} 

जवाब है, मुझे और मेरे सभी साथियों को, जाहिर है:

8 
27 
64 

क्या निम्नलिखित कार्यक्रम से उत्पादन होता है

लेकिन अब कई छात्रों ने इंगित किया है कि जब वे कुछ वातावरण में इसे चलाते हैं तो वे वास्तव में विपरीत होते हैं:

64 
27 
8 

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

ऐसा लगता है कि मुझे एक संकलक अनुकूलन माना जाता है, जो विपरीत क्रम में फ़ंक्शन कॉल का मूल्यांकन करने का चयन करता है, लेकिन मुझे वास्तव में क्यों पता नहीं है। मेरा सवाल है: क्या मेरी धारणाएं सही हैं? क्या पृष्ठभूमि में क्या चल रहा है? या क्या कुछ बिल्कुल अलग है? साथ ही, मैं वास्तव में समझ में नहीं आता कि पीछे के कार्यों का मूल्यांकन करने और फिर आउटपुट का मूल्यांकन करने का लाभ क्यों होगा। ओस्ट्रीम काम के तरीके के कारण आउटपुट आगे बढ़ना होगा, लेकिन ऐसा लगता है कि कार्यों का मूल्यांकन आगे भी होना चाहिए।

आपकी मदद के लिए धन्यवाद!

उत्तर

15

सी ++ मानक को परिभाषित नहीं करता है जो एक पूर्ण अभिव्यक्ति की subexpressions आदेश मूल्यांकन किया जाता है कुछ ऑपरेटरों जो एक आदेश परिचय (अल्पविराम ऑपरेटर, त्रिगुट ऑपरेटर, के अलावा, को लेकर मतभेद है छोटी सर्किटिंग लॉजिकल ऑपरेटर), और तथ्य यह है कि एक अभिव्यक्ति/ऑपरेटर के तर्क/संचालन को बनाने वाले अभिव्यक्तियों का मूल्यांकन फ़ंक्शन/ऑपरेटर से पहले किया जाता है।

जीसीसी आपको (या मुझे) समझाए जाने के लिए बाध्य नहीं है क्योंकि यह उन्हें आदेश देना क्यों चाहता है। यह एक प्रदर्शन अनुकूलन हो सकता है, ऐसा इसलिए हो सकता है क्योंकि संकलक कोड कुछ लाइनों को कम और सरल तरीके से बाहर आया, ऐसा इसलिए हो सकता है क्योंकि मिंगव कोडर में से एक व्यक्तिगत रूप से आपको नफरत करता है, और यह सुनिश्चित करना चाहता है कि यदि आप धारणाएं करते हैं जो ' मानक द्वारा गारंटीकृत, आपका कोड गलत हो जाता है। खुले मानकों की दुनिया में आपका स्वागत है :-)

जोड़ने के लिए संपादित करें: litb परिभाषित व्यवहार के बारे में नीचे एक बिंदु बनाता है। मानक कहता है कि यदि आप किसी अभिव्यक्ति में एक चर को कई बार संशोधित करते हैं, और यदि उस अभिव्यक्ति के मूल्यांकन के वैध क्रम मौजूद हैं, तो चर के बीच अनुक्रम बिंदु के बिना चर को कई बार संशोधित किया गया है, तो अभिव्यक्ति ने व्यवहार को अपरिभाषित किया है। यह यहां लागू नहीं होता है, क्योंकि चर को फ़ंक्शन में कॉल में संशोधित किया जाता है, और किसी फ़ंक्शन कॉल की शुरुआत में अनुक्रम बिंदु होता है (भले ही संकलक इसे रेखांकित करता हो)। हालांकि, यदि आप मैन्युअल रूप से कोड को रेखांकित करेंगे:

std::cout << pow(x++,3) << endl << pow(x++,3) << endl << pow(x++,3) << endl; 

तब यह अपरिभाषित व्यवहार होगा।इस कोड में, यह सभी तीन "x ++" उप-अभिव्यक्तियों का मूल्यांकन करने के लिए कंपाइलर के लिए मान्य है, फिर तीन कॉल को पॉव करने के लिए, फिर operator<< पर विभिन्न कॉल पर प्रारंभ करें। चूंकि यह आदेश मान्य है और x के संशोधन को अलग करने के लिए कोई अनुक्रम बिंदु नहीं है, परिणाम पूरी तरह से अपरिभाषित हैं। आपके कोड स्निपेट में, केवल निष्पादन का क्रम निर्दिष्ट नहीं है।

+0

स्पष्टीकरण के लिए धन्यवाद, मुझे लगा कि यह एक अनिर्धारित कल्पना का एक उदाहरण था, लेकिन अब मैं इसके बारे में सुनिश्चित हो सकता हूं। और यह छात्रों के लिए एक उदाहरण के रूप में खड़ा हो सकता है कि साइड इफेक्ट्स और अत्यधिक जटिल घोंसले क्यों खराब हैं :) –

+5

+1। और विशेष रूप से, ध्यान दें कि कार्यक्रम अपरिभाषित व्यवहार प्रदर्शित नहीं करता है। 'x' अभी भी दो अनुक्रम बिंदुओं के बीच सबसे अधिक बार बदल गया है, क्योंकि फ़ंक्शन कॉल निष्पादन एक-दूसरे को अंतःस्थापित नहीं कर सकते हैं। इसका व्यवहार अनिर्दिष्ट है और cout अभिव्यक्ति कथन के बाद, 'x' का मान होना चाहिए 5. –

+0

@onebyone:" नफरत "भाग बहुत मजेदार है :) :) –

0

हाँ, कार्यात्मक तर्कों के मूल्यांकन का क्रम मानकों के अनुसार "निर्दिष्ट नहीं है"।

इसलिए आउटपुट विभिन्न प्लेटफार्मों

+0

इसी प्रकार std :: cout << x << ++ x << x ++; अनिर्दिष्ट व्यवहार है। नोट: यह अनिर्धारित व्यवहार का आह्वान नहीं करता है। –

+1

@ प्र्रासून, उस मामले में, यह * अपरिभाषित व्यवहार का आह्वान करता है, क्योंकि 'x' का परिवर्तन अनुक्रम बिंदु से' x' के एक दूसरे के परिवर्तन के लिए अब तक ब्रैकेट नहीं किया जाता है। –

+1

@litb: अच्छा अवलोकन! –

2

ऑर्डर जिसमें फ़ंक्शन कॉल पैरामीटर का मूल्यांकन किया गया है, निर्दिष्ट नहीं है। संक्षेप में, आपको उन तर्कों का उपयोग नहीं करना चाहिए जिनके साइड इफेक्ट्स हैं जो कथन के अर्थ और परिणाम को प्रभावित करते हैं।

+0

आपको यह भी समझाएगा कि क्यों (और कैसे) '<< 'वास्तव में एक फ़ंक्शन कॉल है - अन्यथा उत्तर को समझना मुश्किल हो सकता है :) –

0

जैसा कि पहले ही कहा जा चुका है, आप अपरिभाषित व्यवहार के प्रेतवाधित जंगल में भटक गए हैं। प्राप्त करने के लिए क्या हर बार जब आप या तो साइड इफेक्ट को हटा सकते हैं उम्मीद है:

int myFunc(int &x) { 
    int temp = x * x * x; 
    return temp; 
} 

int main() { 
    int x = 2; 
    cout << myFunc(x) << endl << myFunc(x+1) << endl << myFunc(x+2) << endl; 
    //Note that you can't use the increment operator (++) here. It has 
    //side-effects so it will have the same problem 
} 

या तोड़ने समारोह अलग बयान में कहता है:

int myFunc(int &x) { 
    int temp = x * x * x; 
    x += 1; 
    return temp; 
} 

int main() { 
    int x = 2; 
    cout << myFunc(x) << endl; 
    cout << myFunc(x) << endl; 
    cout << myFunc(x) << endl; 
} 

दूसरे संस्करण शायद एक परीक्षण के लिए बेहतर है, के बाद से यह उन्हें साइड इफेक्ट्स पर विचार करने के लिए मजबूर करता है।

+0

हाँ, मैंने पहले ही उनसे उल्लेख किया है कि वे आउटपुट स्टेटमेंट को विभाजित करके अपेक्षित व्यवहार देख सकते हैं। मैं साइड इफेक्ट्स के साथ कोई फ़ंक्शन नहीं होने के साथ सहमत हूं, लेकिन जैसा कि आपने देखा है कि सवाल इस तरह के दुष्प्रभावों के अपने ज्ञान का परीक्षण करने के लिए बहुत विशेष रूप से है। वह और टीए के रूप में मुझे प्रश्नों को बदलने के लिए नहीं मिलता है, बस उन्हें समझाएं :) –

+1

पहला कोड "फिक्स" अपेक्षित व्यवहार का कारण नहीं होगा। व्यक्तिगत उप-अभिव्यक्तियों का मूल्यांकन अभी भी निर्दिष्ट नहीं है, और आपने एक अतिरिक्त समस्या पेश की है: आप अस्थायी रूप से गैर-कॉन्स्ट संदर्भ में पास करते हैं। –

+1

बस एक नाइट, जिसका उल्लेख अन्य उत्तरों में किया गया है, लेकिन तकनीकी रूप से यह अनिर्धारित व्यवहार अपरिभाषित व्यवहार नहीं है। अपरिभाषित व्यवहार का मतलब है कि कुछ भी हो सकता है, जिसमें आपके हार्डड्राइव को स्वरूपित करना, या नाथैक शुरू करना शामिल है। निर्दिष्ट नहीं है कि यह किसी भी क्रम में तर्कों का मूल्यांकन कर सकता है, लेकिन फ़ंक्शन कहने से पहले उन सभी का मूल्यांकन किया जाना चाहिए। – KeithB

10

वास्तव में इसका अनिश्चित व्यवहार क्यों है।

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

// Option 1: Both are members 
cout.operator<<(f1()).operator<< (f2()); 

// Option 2: Both are non members 
operator<< (operator<<(cout, f1()), f2()); 

// Option 3: First is a member, second non-member 
operator<< (cout.operator<<(f1()), f2()); 

// Option 4: First is a non-member, second is a member 
cout.operator<<(f1()).operator<< (f2()); 

:

cout << f1() << f2(); 

इस समारोह कॉल की एक दृश्य है, जहां कॉल की तरह ऑपरेटरों के सदस्यों या गैर सदस्यों किया जा रहा है पर निर्भर करने के लिए विस्तारित किया गया है:

इस और अधिक बुनियादी उदाहरण पर विचार करें निम्नतम स्तर पर ये लगभग समान कोड उत्पन्न करेंगे, इसलिए मैं केवल अब से पहले विकल्प को संदर्भित करूंगा।

वहां मानक में एक गारंटी है कि फ़ंक्शन के शरीर से पहले प्रत्येक फ़ंक्शन कॉल में तर्कों का मूल्यांकन करना चाहिए। इस मामले में, cout.operator<<(f1()) का मूल्यांकन operator<<(f2()) से पहले किया जाना चाहिए, क्योंकि cout.operator<<(f1()) के परिणामस्वरूप अन्य ऑपरेटर को कॉल करने की आवश्यकता है।

अनिर्दिष्ट व्यवहार में शामिल है क्योंकि ऑपरेटरों को कॉल करने का आदेश दिया जाना चाहिए, पर उनके तर्क पर ऐसी कोई आवश्यकता नहीं है।

f2() 
f1() 
cout.operator<<(f1()) 
cout.operator<<(f1()).operator<<(f2()); 

या::

f1() 
f2() 
cout.operator<<(f1()) 
cout.operator<<(f1()).operator<<(f2()); 

या अंत में: इसलिए, जिसके परिणामस्वरूप आदेश से एक हो सकता

f1() 
cout.operator<<(f1()) 
f2() 
cout.operator<<(f1()).operator<<(f2()); 
+0

अच्छा :) मैं कुछ इसी तरह लिखने वाला था, लेकिन मुझे यह देखने के लिए कुछ समय लगा कि वास्तव में गारंटी देता है कि विभिन्न 'ऑपरेटर <<' कॉल अनुक्रम में किए जाते हैं। जैसा कि आपने कहा है कि यह मुझे समझ में आता है, तब से अंतर्निहित ऑब्जेक्ट तर्क को सामान्य फ़ंक्शन तर्क के रूप में देखा जाता है जिसे फ़ंक्शन-एंट्री अनुक्रम बिंदु से पहले मूल्यांकन किया जाता है। मैंने सोचा कि बहस ओवरलोडिंग के उद्देश्य से अस्तित्व में है, लेकिन ऐसा लगता है कि इस दुष्प्रभाव के लिए भी इसका महत्व है। +1 :) –

0

और यही कारण है कि, हर बार जब आप एक पक्ष के साथ एक समारोह लिखने - प्रभाव, भगवान एक बिल्ली का बच्चा मारता है!

संबंधित मुद्दे