मैंने क्या बनाया = i ++ + 1; C ++ 17 में कानूनी?


186

इससे पहले कि आप अपरिभाषित व्यवहार शुरू करें, यह स्पष्ट रूप से N4659 (C ++ 17) में सूचीबद्ध है।

  i = i++ + 1;        // the value of i is incremented

फिर भी N3337 में (C ++ 11)

  i = i++ + 1;        // the behavior is undefined

किया बदल गया?

मैं [N4659 basic.exec] से क्या इकट्ठा कर सकता हूं

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

जहां मूल्य को [N4659 basic.type] पर परिभाषित किया गया है

तुच्छ रूप से कॉपी करने योग्य प्रकारों के लिए, मूल्य प्रतिनिधित्व वस्तु प्रतिनिधित्व में बिट्स का एक सेट है जो एक मूल्य निर्धारित करता है , जो मूल्यों के कार्यान्वयन-परिभाषित सेट का एक असतत तत्व है

से [N3337 basic.exec]

जहां नोट किया गया है, सिवाय इसके कि अलग-अलग संचालकों के संचालनों का मूल्यांकन और अलग-अलग अभिव्यक्तियों के उप-वर्गों का मूल्यांकन न किया जाए। [...] एक ऑपरेटर के ऑपरेंड्स के मूल्य गणना ऑपरेटर के परिणाम के मूल्य गणना से पहले अनुक्रमित किए जाते हैं। यदि एक स्केलर ऑब्जेक्ट पर एक साइड इफेक्ट एक ही स्केलर ऑब्जेक्ट पर या तो एक और साइड इफेक्ट के लिए एक साइड इफेक्ट या एक ही स्केलर ऑब्जेक्ट के मूल्य का उपयोग करते हुए एक मूल्य गणना के सापेक्ष अप्रभावित है, तो व्यवहार अपरिभाषित है।

इसी तरह, मूल्य को [N3337 basic.type] पर परिभाषित किया गया है

तुच्छ रूप से प्रतिलिपि योग्य प्रकारों के लिए, मूल्य प्रतिनिधित्व वस्तु प्रतिनिधित्व में बिट्स का एक सेट है जो एक मूल्य निर्धारित करता है , जो कि मूल्यों के कार्यान्वयन-निर्धारित सेट का एक असतत तत्व है।

वे समरूपता के उल्लेख को छोड़कर समान हैं जो कोई फर्क नहीं पड़ता, और स्केलर ऑब्जेक्ट के बजाय मेमोरी लोकेशन के उपयोग के साथ , जहां

अंकगणित प्रकार, गणना प्रकार, सूचक प्रकार, सदस्य प्रकारों के लिए सूचक std::nullptr_t, और इन प्रकारों के cv- योग्य संस्करणों को सामूहिक रूप से स्केलर प्रकार कहा जाता है।

जो उदाहरण को प्रभावित नहीं करता है।

से [N4659 expr.ass]

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

से [N3337 expr.ass]

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

N3337 में अंतिम वाक्य अनुपस्थित होने का एकमात्र अंतर है।

हालांकि अंतिम वाक्य का कोई महत्व नहीं होना चाहिए क्योंकि बाएं ऑपरेंड iन तो "एक और साइड इफेक्ट" है और न ही "समान स्केलर ऑब्जेक्ट के मूल्य का उपयोग" के रूप में आईडी-एक्सप्रेशन एक अंतराल है।


23
आपने कारण की पहचान की है: C ++ 17 में, दाहिने ऑपरेंड को बाएं ऑपरेंड से पहले अनुक्रमित किया गया है। C ++ 11 में ऐसा कोई अनुक्रमण नहीं था। क्या, ठीक है, तुम्हारा सवाल है?
Rob

4
@ रोब @ अंतिम वाक्य देखें।
राहगीर

7
क्या किसी के पास इस बदलाव की प्रेरणा की कड़ी है? मैं एक स्थिर विश्लेषक को यह कहना चाहूंगा कि जब आप कोड के साथ सामना करना चाहते हैं, तो आप "ऐसा नहीं करना चाहते" i = i++ + 1;

7
@ निल बटरवर्थ, यह पेपर p0145r3.pdf से है : "रिफाइनिंग एक्सप्रेशन इवैल्यूएशन ऑर्डर फॉर इडियोमीटिक सी ++।"
xaizek

9
@NeilButterworth, अनुभाग संख्या 2 का कहना है कि यह काउंटर सहज है और यहां तक ​​कि विशेषज्ञ सभी मामलों में सही काम करने में विफल रहते हैं। यह उनकी प्रेरणा से बहुत अधिक है।
xaizek

जवाबों:


144

सी ++ 11 में "असाइनमेंट" का कार्य, अर्थात एलएचएस को संशोधित करने का दुष्प्रभाव, सही ऑपरेंड के मूल्य गणना के बाद अनुक्रमित है । ध्यान दें कि यह एक अपेक्षाकृत "कमजोर" गारंटी है: यह आरएचएस के मूल्य गणना के संबंध में केवल अनुक्रमण पैदा करता है । यह आरएचएस में मौजूद दुष्प्रभावों के बारे में कुछ भी नहीं कहता है , क्योंकि दुष्प्रभाव की घटना मूल्य गणना का हिस्सा नहीं है । सी ++ 11 की आवश्यकताएं असाइनमेंट के कार्य और आरएचएस के किसी भी दुष्प्रभाव के बीच कोई सापेक्ष अनुक्रमण स्थापित नहीं करती हैं। यह वह है जो यूबी के लिए क्षमता बनाता है।

इस मामले में एकमात्र आशा आरएचएस में उपयोग किए गए विशिष्ट ऑपरेटरों द्वारा की गई कोई अतिरिक्त गारंटी है। यदि RHS उपसर्ग का उपयोग ++करता है, तो उपसर्ग के रूप में विशिष्ट अनुक्रमण गुण ++इस उदाहरण में दिन को बचाएंगे। लेकिन पोस्टफिक्स ++एक अलग कहानी है: यह ऐसी गारंटी नहीं देता है। C ++ 11 में इस उदाहरण में एक दूसरे के साथ संबंध के बिना =और ++उपसर्ग के दुष्प्रभाव समाप्त होते हैं। और वह यूबी है।

C ++ 17 में असाइनमेंट ऑपरेटर के विनिर्देश में एक अतिरिक्त वाक्य जोड़ा गया है:

दाहिने ऑपरेंड को बाएं ऑपेंड से पहले अनुक्रमित किया जाता है।

उपरोक्त के साथ संयोजन में यह बहुत मजबूत गारंटी देता है। यह एलएचएस में होने वाली हर चीज से पहले आरएचएस (किसी भी साइड-इफेक्ट सहित) में होने वाली हर चीज का अनुक्रम करता है । चूंकि वास्तविक असाइनमेंट एलएचएस (और आरएचएस) के बाद अनुक्रमित है , इसलिए अतिरिक्त सीक्वेंसिंग आरएचएस में मौजूद किसी भी साइड-इफेक्ट से असाइनमेंट के कार्य को पूरी तरह से अलग करता है। यह मजबूत अनुक्रमण है जो उपरोक्त यूबी को समाप्त करता है।

(@Joll बोलिंगर की टिप्पणियों को ध्यान में रखने के लिए अपडेट किया गया।)


3
क्या इस अंश में "बाएं हाथ के ऑपरेंड" द्वारा कवर किए गए प्रभावों में "असाइनमेंट का वास्तविक कार्य" शामिल करना वास्तव में सही है? मानक में वास्तविक असाइनमेंट की अनुक्रमण के बारे में अलग भाषा है। मैं आपके द्वारा प्रस्तुत किए गए अंश को बाएं-दाएं और दाएं हाथ के उप-अभिव्यक्तियों की अनुक्रमण तक सीमित होने के लिए प्रस्तुत करता हूं, जो कि उस खंड के बाकी हिस्सों के साथ संयोजन में पर्याप्त नहीं लगता है, अच्छी तरह से समर्थन करने के लिए- ओपी के बयान की परिभाषा।
जॉन बोलिंगर ने

11
सुधार: वास्तविक संचालन अभी भी बाएं ऑपरेंड के मूल्य गणना के बाद अनुक्रमित है, और बाएं ऑपरेंड का मूल्यांकन सही ऑपरेंड के मूल्यांकन (पूर्ण) के बाद किया जाता है, इसलिए हां, यह परिवर्तन अच्छी तरह से परिभाषित ओपी का समर्थन करने के लिए पर्याप्त है के बारे में पूछा। मैं सिर्फ विवरणों को समझा रहा हूं, लेकिन ये मायने रखते हैं, क्योंकि अलग-अलग कोड के लिए उनके अलग-अलग निहितार्थ हो सकते हैं।
जॉन बोलिंगर ने

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

1
@ काज: कंपाउंड असाइनमेंट के लिए, राइट साइड के बाद लेफ्ट-साइड वैल्यू मूल्यांकन का प्रदर्शन करने की अनुमति देता है जैसे x -= y;कि mov eax,[y] / sub [x],eaxइसके बजाय कुछ संसाधित किया जाना चाहिए mov eax,[x] / neg eax / add eax,[y] / mov [x],eax। मुझे इसके बारे में कुछ भी पता नहीं है। यदि किसी को एक ऑर्डरिंग निर्दिष्ट करना होता है, तो सबसे कुशल ऑर्डर लेफ्ट-साइड ऑब्जेक्ट को पहचानने के लिए आवश्यक सभी संगणनाएं करना होगा , फिर सही ऑपरेंड का मूल्यांकन करेगा, फिर लेफ्ट ऑब्जेक्ट का मान , लेकिन इसके लिए एक टर्म की आवश्यकता होगी बाईं वस्तु की मूढ़ता को हल करने के कार्य के लिए।
सुपरकैट

1
@Kaz: यदि xऔर yथे volatile, कि दुष्प्रभाव पड़ेगा। इसके अलावा, वही विचार लागू होगा x += f();, जहां f()संशोधित होता है x
सुपरकैट

33

आपने नए वाक्य की पहचान की

दाहिने ऑपरेंड को बाएं ऑपेंड से पहले अनुक्रमित किया जाता है।

और आपने सही पहचाना कि लेवल के रूप में लेफ्ट ऑपरेंड का मूल्यांकन अप्रासंगिक है। हालाँकि, इससे पहले अनुक्रमित होना एक सकर्मक संबंध होना निर्दिष्ट है। पूर्ण अधिकार ऑपरेंड (पोस्ट-इन्क्रीमेंट सहित) इसलिए असाइनमेंट से पहले भी अनुक्रमित है। C ++ 11 में, असाइनमेंट से पहले केवल सही ऑपरेंड का मान गणना किया गया था।


7

पुराने C ++ मानकों और C11 में, असाइनमेंट ऑपरेटर टेक्स्ट की परिभाषा टेक्स्ट के साथ समाप्त होती है:

ऑपरेंड का मूल्यांकन अप्राप्त है।

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

यह पाठ केवल C ++ 11 में हटा दिया गया था, जिससे यह कुछ अस्पष्ट हो गया। यह यूबी है या नहीं है? यह C ++ 17 में स्पष्ट किया गया है जहां उन्होंने जोड़ा:

दाहिने ऑपरेंड को बाएं ऑपेंड से पहले अनुक्रमित किया जाता है।


एक साइड नोट के रूप में, यहां तक ​​कि पुराने मानकों में, यह सब बहुत स्पष्ट किया गया था, उदाहरण के लिए C99:

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

मूल रूप से, C11 / C ++ 11 में, उन्होंने इस पाठ को हटाते समय गड़बड़ कर दी।


1

यह अन्य उत्तरों के लिए आगे की जानकारी है और मैं इसे पोस्ट कर रहा हूं क्योंकि नीचे दिए गए कोड के बारे में अक्सर पूछा जाता है

अन्य उत्तरों में स्पष्टीकरण सही है और निम्नलिखित कोड पर भी लागू होता है जो अब अच्छी तरह से परिभाषित है (और संग्रहीत मूल्य को नहीं बदलता है i):

i = i++;

+ 1एक रेड हेरिंग है और यह वास्तव में, स्पष्ट नहीं कर रहा है यही कारण है कि स्टैंडर्ड उनके उदाहरण में इसका इस्तेमाल किया, हालांकि मैं सी ++ 11 कि शायद करने से पहले मेलिंग सूची पर बहस कर याद करते हैं लोग + 1एक नई पहल की वजह से दाएं पर जल्दी lvalue रूपांतरण के लिए मजबूर करने के लिए हाथ की तरफ। निश्चित रूप से उनमें से कोई भी C ++ 17 में लागू नहीं होता है (और शायद C ++ के किसी भी संस्करण में कभी भी लागू नहीं होता है)।

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