क्या यह कोड "सी ++ प्रोग्रामिंग लैंग्वेज" से 4 वें संस्करण के खंड 36.3.6 में अच्छी तरह से परिभाषित व्यवहार है?


94

बज़्ने स्ट्रॉस्ट्रुप के सी + + प्रोग्रामिंग लैंग्वेज के चौथे संस्करण खंड 36.3.6 एसटीएल-जैसे ऑपरेशंस में निम्नलिखित कोड का उपयोग जंजीर के उदाहरण के रूप में किया जाता है :

void f2()
{
    std::string s = "but I have heard it works even if you don't believe in it" ;
    s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
        .replace( s.find( " don't" ), 6, "" );

    assert( s == "I have heard it works only if you believe in it" ) ;
}

मुखर gcc( इसे लाइव देखें ) और Visual Studio( इसे लाइव देखें ) में विफल रहता है , लेकिन क्लैंग ( इसे लाइव देखें ) का उपयोग करते समय यह विफल नहीं होता है ।

मुझे अलग परिणाम क्यों मिल रहे हैं? क्या इनमें से कोई भी कंपाइलर गलत तरीके से चैनिंग एक्सप्रेशन का मूल्यांकन कर रहा है या क्या यह कोड किसी अनिर्दिष्ट या अपरिभाषित व्यवहार का प्रदर्शन करता है ?


बेहतर:s.replace( s.replace( s.replace(0, 4, "" ).find( "even" ), 4, "only" ).find( " don't" ), 6, "" );
बेन वोइगट

20
एक तरफ बग, क्या मैं अकेला हूं जो बदसूरत कोड सोचता है जैसे कि किताब में नहीं होना चाहिए?
कारोली होर्वाथ

5
@KarolyHorvath ध्यान दें कि cout << a << b << coperator<<(operator<<(operator<<(cout, a), b), c)केवल मामूली कम बदसूरत है।
ओकेटलिस्ट

1
@Oktalist: :) कम से कम मुझे वहाँ इरादा है। यह तर्क-निर्भर नाम लुकअप और ऑपरेटर सिंटैक्स को एक ही प्रारूप में सिखाता है ... और यह इस बात का आभास नहीं देता है कि आपको वास्तव में इस तरह कोड लिखना चाहिए।
कारोली होरवाथ

जवाबों:


104

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

इस उदाहरण में प्रस्ताव N4228 का उल्लेख किया गया है : Idiomatic C ++ के लिए शोधन अभिव्यक्ति मूल्यांकन आदेश जो प्रश्न में कोड के बारे में निम्नलिखित कहता है:

[...] इस कोड की समीक्षा C ++ विशेषज्ञों द्वारा विश्वव्यापी की गई है, और प्रकाशित (C ++ प्रोग्रामिंग लैंग्वेज, 4 वें संस्करण।) फिर भी, मूल्यांकन के अनिर्दिष्ट आदेश के लिए इसकी भेद्यता केवल एक उपकरण द्वारा खोजी गई है [।। ।]

विवरण

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

पहली नज़र में यह प्रतीत हो सकता है कि चूंकि प्रत्येक replaceको बाएं से दाएं मूल्यांकन किया जाना है कि संबंधित फ़ंक्शन तर्क समूहों का मूल्यांकन बाएं से दाएं समूहों के रूप में भी किया जाना चाहिए।

यह गलत है, फ़ंक्शन तर्कों का मूल्यांकन का अनिर्दिष्ट क्रम है, हालांकि फंक्शन कॉलिंग चैन प्रत्येक फ़ंक्शन कॉल के लिए बाएं से दाएं मूल्यांकन क्रम का परिचय देता है, प्रत्येक फ़ंक्शन कॉल के तर्क केवल सदस्य फ़ंक्शन कॉल के संबंध में अनुक्रमित होते हैं। का। विशेष रूप से यह निम्नलिखित कॉल को प्रभावित करता है:

s.find( "even" )

तथा:

s.find( " don't" )

जो अनिश्चित काल के लिए सम्मान के साथ अनुक्रमित हैं:

s.replace(0, 4, "" )

दो findकॉल का मूल्यांकन इससे पहले या बाद में किया जा सकता है replace, जो मायने रखता है क्योंकि यह sएक तरह से साइड इफेक्ट है जो परिणाम को बदल देगा find, यह लंबाई को बदलता है s। इसलिए जब replaceदोनों findकॉल के सापेक्ष मूल्यांकन किया जाता है तो परिणाम भिन्न होगा।

यदि हम जंजीर की अभिव्यक्ति को देखते हैं और कुछ उप-अभिव्यक्तियों के मूल्यांकन क्रम की जांच करते हैं:

s.replace(0, 4, "" ).replace( s.find( "even" ), 4, "only" )
^ ^       ^  ^  ^    ^        ^                 ^  ^
A B       |  |  |    C        |                 |  |
          1  2  3             4                 5  6

तथा:

.replace( s.find( " don't" ), 6, "" );
 ^        ^                   ^  ^
 D        |                   |  |
          7                   8  9

ध्यान दें, हम इस तथ्य की अनदेखी कर रहे हैं कि 4और 7अधिक उप-अभिव्यक्तियों में टूट सकता है। इसलिए:

  • Aइससे पहले Bजो अनुक्रम Cकिया जाता है उससे पहले अनुक्रम किया जाता हैD
  • 1के 9धुंधलेपन से नीचे सूचीबद्ध अपवादों में से कुछ के साथ अन्य उप भाव के संबंध में अनुक्रम कर रहे हैं
    • 1से 3पहले सिलवाया जाता हैB
    • 4से 6पहले सिलवाया जाता हैC
    • 7से 9पहले सिलवाया जाता हैD

इस मुद्दे की कुंजी यह है कि:

  • 4करने के 9लिए सम्मान के साथ अनिश्चित काल के अनुक्रम हैंB

के लिए मूल्यांकन पसंद के संभावित आदेश 4और 7के संबंध में Bके बीच परिणामों में अंतर बताते हैं clangऔर gccजब का मूल्यांकन f2()। मेरी परीक्षणों में clangमूल्यांकन करता है Bमूल्यांकन करने से पहले 4और 7जबकि gccयह मूल्यांकन करता है के बाद। प्रत्येक मामले में क्या हो रहा है, यह प्रदर्शित करने के लिए हम निम्नलिखित परीक्षण कार्यक्रम का उपयोग कर सकते हैं:

#include <iostream>
#include <string>

std::string::size_type my_find( std::string s, const char *cs )
{
    std::string::size_type pos = s.find( cs ) ;
    std::cout << "position " << cs << " found in complete expression: "
        << pos << std::endl ;

    return pos ;
}

int main()
{
   std::string s = "but I have heard it works even if you don't believe in it" ;
   std::string copy_s = s ;

   std::cout << "position of even before s.replace(0, 4, \"\" ): " 
         << s.find( "even" ) << std::endl ;
   std::cout << "position of  don't before s.replace(0, 4, \"\" ): " 
         << s.find( " don't" ) << std::endl << std::endl;

   copy_s.replace(0, 4, "" ) ;

   std::cout << "position of even after s.replace(0, 4, \"\" ): " 
         << copy_s.find( "even" ) << std::endl ;
   std::cout << "position of  don't after s.replace(0, 4, \"\" ): "
         << copy_s.find( " don't" ) << std::endl << std::endl;

   s.replace(0, 4, "" ).replace( my_find( s, "even" ) , 4, "only" )
        .replace( my_find( s, " don't" ), 6, "" );

   std::cout << "Result: " << s << std::endl ;
}

परिणाम gcc( इसे लाइव देखें )

position of even before s.replace(0, 4, "" ): 26
position of  don't before s.replace(0, 4, "" ): 37

position of even after s.replace(0, 4, "" ): 22
position of  don't after s.replace(0, 4, "" ): 33

position  don't found in complete expression: 37
position even found in complete expression: 26

Result: I have heard it works evenonlyyou donieve in it

परिणाम clang( इसे लाइव देखें ):

position of even before s.replace(0, 4, "" ): 26
position of  don't before s.replace(0, 4, "" ): 37

position of even after s.replace(0, 4, "" ): 22
position of  don't after s.replace(0, 4, "" ): 33

position even found in complete expression: 22
position don't found in complete expression: 33

Result: I have heard it works only if you believe in it

परिणाम Visual Studio( इसे लाइव देखें ):

position of even before s.replace(0, 4, "" ): 26
position of  don't before s.replace(0, 4, "" ): 37

position of even after s.replace(0, 4, "" ): 22
position of  don't after s.replace(0, 4, "" ): 33

position  don't found in complete expression: 37
position even found in complete expression: 26
Result: I have heard it works evenonlyyou donieve in it

मानक से विवरण

हम जानते हैं कि जब तक उप-अभिव्यक्तियों के मूल्यांकन को निर्दिष्ट नहीं किया जाता है, यह C ++ 11 मानक अनुभाग 1.9 प्रोग्राम निष्पादन के मसौदे से है जो निम्न है:

जहां नोट किया गया है, सिवाय इसके कि अलग-अलग संचालकों के संचालनों का मूल्यांकन और अलग-अलग अभिव्यक्तियों के उप-संदर्भों का मूल्यांकन न किया जाए। […]

और हम जानते हैं कि एक फ़ंक्शन कॉल फ़ंक्शन के संबंध से पहले एक अनुक्रम का परिचय देता है, जो फ़ंक्शन बॉडी के संबंध में पोस्टफ़िक्स अभिव्यक्ति और तर्कों को अनुभाग से कहता है 1.9:

[...] एक फ़ंक्शन को कॉल करते समय (फ़ंक्शन इनलाइन है या नहीं), किसी भी तर्क अभिव्यक्ति के साथ जुड़े प्रत्येक मूल्य संगणना और साइड इफेक्ट, या पोस्ट फ़ंक्शन को पोस्ट फ़ंक्शन को नामित करने वाले अभिव्यक्ति के साथ, प्रत्येक अभिव्यक्ति या कथन के निष्पादन से पहले अनुक्रमित किया जाता है। कहा समारोह के शरीर में [...]

हम यह भी जानते हैं कि क्लास मेंबर एक्सेस और इसलिए चैनिंग, 5.2.5 क्लास क्लास मेंबर एक्सेस से लेफ्ट से राइट की ओर मूल्यांकन करेगा, जो कहता है:

[...] बिंदु या तीर से पहले उपसर्ग अभिव्यक्ति का मूल्यांकन किया जाता है; 64 उस मूल्यांकन का परिणाम, आईडी-एक्सप्रेशन के साथ, पूरे पोस्टफिक्स एक्सप्रेशन के परिणाम को निर्धारित करता है।

ध्यान दें, उस स्थिति में जहां आईडी-एक्सप्रेशन एक गैर-स्थैतिक सदस्य फ़ंक्शन के रूप में समाप्त होता है, यह अभिव्यक्ति-सूची के मूल्यांकन के आदेश को निर्दिष्ट नहीं करता है ()क्योंकि यह एक अलग उप-अभिव्यक्ति है। 5.2 पोस्टफ़िक्स अभिव्यक्तियों से संबंधित व्याकरण :

postfix-expression:
    postfix-expression ( expression-listopt)       // function call
    postfix-expression . templateopt id-expression // Class member access, ends
                                                   // up as a postfix-expression

C ++ 17 परिवर्तन

प्रस्ताव p0145r3: परिष्कृत अभिव्यक्ति मूल्यांकन के लिए मुहावरेदार सी ++ ने कई बदलाव किए। पोस्टफ़िक्स-एक्सप्रेशन और उनकी अभिव्यक्ति-सूची के लिए मूल्यांकन नियमों के क्रम को मजबूत करके कोड को अच्छी तरह से निर्दिष्ट व्यवहार देने वाले परिवर्तन शामिल हैं ।

[expr.call] p5 कहते हैं:

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

void f() {
std::string s = "but I have heard it works even if you don’t believe in it";
s.replace(0, 4, "").replace(s.find("even"), 4, "only").replace(s.find(" don’t"), 6, "");
assert(s == "I have heard it works only if you believe in it"); // OK
}

उदाहरण का]


7
मुझे यह देखकर थोड़ा आश्चर्य हुआ कि "कई विशेषज्ञों" ने समस्या की अनदेखी की, यह अच्छी तरह से ज्ञात है कि फ़ंक्शन कॉल के पोस्टफिक्स-एक्सप्रेशन का मूल्यांकन करना अनुक्रमित नहीं है - तर्कों (सी और सी ++ के सभी संस्करणों में) का मूल्यांकन करने से पहले।
MM

@ShafikYaghmour फ़ंक्शन कॉल अनिश्चित काल तक एक-दूसरे के संबंध में और बाकी सब चीज़ों के साथ सिलसिलेवार होते हैं, जो आपके द्वारा बताए गए अनुक्रम वाले रिश्तों से पहले। हालाँकि, 1, 2, 3, 5, 6, 8, 9 "even", "don't"और कई उदाहरणों का sमूल्यांकन एक दूसरे के सापेक्ष अप्रयुक्त हैं।
टीसी

4
@ यह नहीं है (यह "बग" कैसे उत्पन्न होता है)। उदाहरण के लिए foo().func( bar() ), यह कॉल करने foo()से पहले या बाद में कॉल कर सकता है bar()पोस्टफ़िक्स अभिव्यक्ति है foo().func। तर्क और उपसर्ग-अभिव्यक्ति शरीर के सामने अनुक्रमित हैं func(), लेकिन एक-दूसरे के सापेक्ष अप्रयुक्त।
MM

@MattMcNabb आह, सही है, मैंने गलत पढ़ा। आप कॉल के बजाय पोस्टफिक्स-एक्सप्रेशन की ही बात कर रहे हैं । हां, यह सही है, वे बिना शर्त (जब तक कि कुछ अन्य नियम लागू नहीं होते हैं)।
TC

6
वहाँ भी कारक है कि एक B.Stroustrup पुस्तक में दिखाई देने वाले कोड को सही मान लेता है अन्यथा कोई निश्चित रूप से पहले से ही देखा होगा! (संबंधित; SO उपयोगकर्ता अभी भी K & R में नई गलतियाँ पाते हैं)
MM

4

इसका उद्देश्य C ++ 17 के संबंध में मामले पर जानकारी जोड़ना है। ऊपर दिए गए कोड का हवाला देते हुए इस मुद्दे को संबोधित करने के लिए प्रस्ताव ( रिफाइनिंग एक्सप्रेशन इवैल्यूएशन ऑर्डर फॉर इडियोमैटिक सी ++ रिवीजन 2 ) को C++17संबोधित किया गया था।

जैसा कि सुझाव दिया गया है, मैंने प्रस्ताव से प्रासंगिक जानकारी जोड़ी और (मेरा प्रकाश डाला):

अभिव्यक्ति के मूल्यांकन का क्रम, जैसा कि वर्तमान में मानक में निर्दिष्ट है, सलाह, लोकप्रिय प्रोग्रामिंग मुहावरे, या मानक पुस्तकालय सुविधाओं की सापेक्ष सुरक्षा को कमजोर करता है। जाल केवल नौसिखियों या लापरवाह प्रोग्रामर के लिए नहीं हैं। वे हम सभी को अंधाधुंध तरीके से प्रभावित करते हैं, तब भी जब हम नियमों को जानते हैं।

निम्नलिखित कार्यक्रम के टुकड़े पर विचार करें:

void f()
{
  std::string s = "but I have heard it works even if you don't believe in it"
  s.replace(0, 4, "").replace(s.find("even"), 4, "only")
      .replace(s.find(" don't"), 6, "");
  assert(s == "I have heard it works only if you believe in it");
}

मुखबिर को प्रोग्रामर के इच्छित परिणाम को मान्य करना चाहिए। यह एक सामान्य मानक अभ्यास के सदस्य फ़ंक्शन कॉल के "चेनिंग" का उपयोग करता है। इस कोड की समीक्षा C ++ विशेषज्ञों द्वारा विश्वव्यापी की गई है, और (C ++ प्रोग्रामिंग लैंग्वेज, 4th संस्करण।) प्रकाशित किया गया है, फिर भी, मूल्यांकन के अनिर्दिष्ट आदेश के लिए इसकी भेद्यता केवल एक उपकरण द्वारा हाल ही में खोजी गई है।

कागज ने C++17अभिव्यक्ति के मूल्यांकन के क्रम पर पूर्व- नियम को बदलने का सुझाव दिया, जो Cतीन दशकों से अधिक से प्रभावित था और मौजूद था। इसने प्रस्तावित किया कि भाषा को समकालीन मुहावरों या जोखिम "जाल और अस्पष्ट के स्रोतों की गारंटी देनी चाहिए , कीड़े खोजने के लिए कठिन" जैसे कि ऊपर दिए गए कोड नमूने के साथ क्या हुआ।

प्रस्ताव के लिए C++17करने के लिए है कि हर अभिव्यक्ति एक अच्छी तरह से परिभाषित मूल्यांकन आदेश है की आवश्यकता होती है :

  • उपसर्ग अभिव्यक्तियों का मूल्यांकन बाएं से दाएं किया जाता है। इसमें फ़ंक्शन कॉल और सदस्य चयन अभिव्यक्तियाँ शामिल हैं।
  • असाइनमेंट अभिव्यक्तियों का मूल्यांकन दाईं से बाईं ओर किया जाता है। इसमें कंपाउंड असाइनमेंट शामिल हैं।
  • ऑपरेटर्स को शिफ्ट करने वाले ऑपरेटरों का मूल्यांकन बाएं से दाएं किया जाता है।
  • एक अधिभार ऑपरेटर को शामिल करने वाले अभिव्यक्ति के मूल्यांकन का क्रम संबंधित बिल्ट-इन ऑपरेटर से जुड़े आदेश से निर्धारित होता है, न कि फ़ंक्शन कॉल के लिए नियमों से।

उपरोक्त कोड सफलतापूर्वक GCC 7.1.1और का उपयोग कर संकलन करता है Clang 4.0.0

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.