एफ (i = -1, i = -1) अपरिभाषित व्यवहार क्यों है?


267

मैं मूल्यांकन उल्लंघन के आदेश के बारे में पढ़ रहा था , और वे एक उदाहरण देते हैं जो मुझे पहेली करता है।

1) अगर एक स्केलर ऑब्जेक्ट पर एक साइड इफेक्ट, एक ही स्केलर ऑब्जेक्ट पर दूसरे साइड इफेक्ट के सापेक्ष अन-सेक्विन है, तो व्यवहार अपरिभाषित है।

// snip
f(i = -1, i = -1); // undefined behavior

इस संदर्भ में, iएक स्केलर ऑब्जेक्ट है , जिसका स्पष्ट अर्थ है

अंकगणित प्रकार (3.9.1), एन्यूमरेशन प्रकार, पॉइंटर प्रकार, पॉइंटर से सदस्य प्रकार (3.9.2), एसटीडी :: nullptr_t, और इन प्रकारों के सीवी-योग्य संस्करण (3.9.3) सामूहिक रूप से स्केलर प्रकार के होते हैं।

मैं यह नहीं देखता कि उस मामले में बयान कितना अस्पष्ट है। यह मुझे लगता है कि चाहे पहले या दूसरे तर्क का मूल्यांकन पहले किया जाए, iसमाप्त होता है -1, और दोनों तर्क भी हैं -1

कृपया कोई स्पष्ट कर सकता है?


अपडेट करें

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


सारांश

चूँकि इस प्रश्न पर एक टन ध्यान गया, इसलिए मैं मुख्य बिंदुओं / उत्तरों को संक्षेप में प्रस्तुत करूँगा। सबसे पहले, मुझे यह बताने के लिए एक छोटे से विषयांतर की अनुमति दें कि "क्यों" में निकटता से संबंधित अलग-अलग अर्थ हो सकते हैं, जैसे "किस कारण से ", "किस कारण से ", और "किस उद्देश्य से "। मैं "क्यों" के उन अर्थों में से उत्तर को समूहित करूंगा, जिन्हें उन्होंने संबोधित किया था।

किस कारण से

यहाँ पर मुख्य उत्तर पॉल ड्रेपर से आया है , जिसमें मार्टिन जे का योगदान समान है, लेकिन व्यापक उत्तर के रूप में नहीं। पॉल ड्रेपर का जवाब उबल पड़ता है

यह अपरिभाषित व्यवहार है क्योंकि यह परिभाषित नहीं है कि व्यवहार क्या है।

C ++ मानक क्या कहता है, यह समझाने के संदर्भ में उत्तर कुल मिलाकर बहुत अच्छा है। यह यूबी के कुछ संबंधित मामलों को भी संबोधित करता है जैसे f(++i, ++i);और f(i=1, i=-1);। संबंधित मामलों के पहले में, यह स्पष्ट नहीं है कि क्या पहला तर्क होना चाहिए i+1और दूसरा i+2या इसके विपरीत; दूसरे में, यह स्पष्ट नहीं है iकि फ़ंक्शन कॉल के बाद 1 या -1 होना चाहिए। ये दोनों मामले यूबी हैं क्योंकि वे निम्नलिखित नियम के तहत आते हैं:

यदि एक स्केलर ऑब्जेक्ट पर एक साइड इफेक्ट एक ही स्केलर ऑब्जेक्ट पर एक और साइड इफेक्ट के सापेक्ष आरोपित नहीं किया जाता है, तो व्यवहार अपरिभाषित है।

इसलिए, f(i=-1, i=-1)यूबी भी है क्योंकि यह एक ही नियम के तहत आता है, इसके बावजूद प्रोग्रामर का इरादा स्पष्ट (स्पष्ट) और स्पष्ट नहीं है।

पॉल ड्रेपर ने अपने निष्कर्ष में यह भी स्पष्ट किया है कि

क्या यह परिभाषित व्यवहार हो सकता था? हाँ। क्या इसे परिभाषित किया गया था? नहीं।

जो हमें "किस कारण / उद्देश्य के लिए f(i=-1, i=-1)अपरिभाषित व्यवहार के रूप में छोड़ दिया गया था" के सवाल पर लाता है ?

किस कारण / उद्देश्य से

यद्यपि C ++ मानक में कुछ ओवरसाइट्स (शायद लापरवाह) हैं, कई चूक अच्छी तरह से तर्कपूर्ण हैं और एक विशिष्ट उद्देश्य की पूर्ति करते हैं। हालांकि मुझे पता है कि उद्देश्य अक्सर "संकलक-लेखक की नौकरी को आसान बनाते हैं", या "तेज कोड" है, मुझे मुख्य रूप से यह जानने में दिलचस्पी थी कि क्या यूबी के रूप में एक अच्छा कारण है f(i=-1, i=-1)

हार्मिक और सुपरकैट मुख्य उत्तर प्रदान करते हैं जो यूबी के लिए एक कारण प्रदान करते हैं । हार्मिक बताते हैं कि एक अनुकूलन कंपाइलर जो कई मशीन निर्देशों में अस्थिर रूप से परमाणु असाइनमेंट संचालन को तोड़ सकता है, और यह इष्टतम गति के लिए उन निर्देशों को आगे इंटरलेक्ट कर सकता है। यह कुछ बहुत ही आश्चर्यजनक परिणाम दे सकता है: iअपने परिदृश्य में -2 के रूप में समाप्त होता है! इस प्रकार, हार्मोनिक दर्शाता है कि एक ही मान को एक से अधिक चर पर कैसे असाइन किया जाए तो इसके दुष्प्रभाव हो सकते हैं यदि ऑपरेशन अप्रयुक्त हैं।

सुपरकैट f(i=-1, i=-1)ऐसा करने के लिए प्राप्त करने की कोशिश करने के नुकसान की एक संबंधित प्रदर्शनी प्रदान करता है जैसा कि ऐसा लगता है कि इसे करना चाहिए। वह बताते हैं कि कुछ आर्किटेक्चर पर, एक ही मेमोरी एड्रेस पर एक साथ कई राइट्स लिखने पर कड़ी पाबंदी है। एक कंपाइलर को इसे पकड़ने में मुश्किल समय हो सकता है अगर हम कुछ तुच्छ से कम काम कर रहे हैं f(i=-1, i=-1)

डेविडफ़ भी हार्मोनिक के समान इंटरलेविंग निर्देशों का एक उदाहरण प्रदान करता है।

हालांकि हरिकेस के प्रत्येक, सुपरकैट और डेविफ के उदाहरण कुछ हद तक वंचित हैं, एक साथ लिया गया है, फिर भी वे एक ठोस कारण प्रदान करने के लिए सेवा करते हैं कि f(i=-1, i=-1)अपरिभाषित व्यवहार क्यों होना चाहिए।

मैंने हार्मिक के उत्तर को स्वीकार किया क्योंकि इसने सभी अर्थों को संबोधित करने का सबसे अच्छा काम किया, भले ही पॉल ड्रेपर के जवाब ने "किस कारण से" भाग को बेहतर तरीके से संबोधित किया।

अन्य जवाब

जॉनबी बताते हैं कि यदि हम ओवरलोड असाइनमेंट ऑपरेटरों (सिर्फ सादे स्केलर के बजाय) पर विचार करते हैं, तो हम मुसीबत में भी दौड़ सकते हैं।


1
एक स्केलर ऑब्जेक्ट स्केलर प्रकार का एक ऑब्जेक्ट है। 3.9 / 9 देखें: "अंकगणित प्रकार (3.9.1), एन्यूमरेशन प्रकार, पॉइंटर प्रकार, पॉइंटर से सदस्य प्रकार (3.9.2) std::nullptr_t, और इन प्रकारों के सीवी-योग्य संस्करण (3.9.3) को सामूहिक रूप से स्केलर प्रकार कहा जाता है । "
रोब कैनेडी

1
हो सकता है कि पृष्ठ पर कोई त्रुटि हो, और वे वास्तव में मतलब f(i-1, i = -1)या कुछ इसी तरह का था।
मिस्टर लिस्टर

इस प्रश्न पर एक नज़र डालें: stackoverflow.com/a/4177063/71074
रॉबर्ट एस। बार्न्स

@RobKennedy साभार क्या "अंकगणित प्रकार" में बूल शामिल है?
निकु स्टियाका

1
SchighSchagh आपका अपडेट उत्तर अनुभाग में होना चाहिए।
बृजेश चौहान

जवाबों:


343

चूंकि ऑपरेशन निष्कासित किए गए हैं, इसलिए यह कहने के लिए कुछ भी नहीं है कि असाइनमेंट करने वाले निर्देशों को हस्तक्षेप नहीं किया जा सकता है। सीपीयू आर्किटेक्चर के आधार पर ऐसा करना इष्टतम हो सकता है। संदर्भित पृष्ठ यह बताता है:

यदि A को B से पहले अनुक्रमित नहीं किया गया है और A से पहले B का अनुक्रम नहीं किया गया है, तो दो संभावनाएँ मौजूद हैं:

  • ए और बी के मूल्यांकन निष्फल हैं: वे किसी भी क्रम में किए जा सकते हैं और ओवरलैप हो सकते हैं (निष्पादन के एक ही थ्रेड के भीतर, कंपाइलर सीपीयू निर्देशों को एनालाइज कर सकता है जिसमें ए और बी शामिल हैं)

  • A और B का मूल्यांकन अनिश्चित-अनुक्रमित है: वे किसी भी क्रम में किए जा सकते हैं, लेकिन ओवरलैप नहीं हो सकते हैं: A, B से पहले पूर्ण होगा या B, A से पहले पूर्ण होगा। यह क्रम अगली बार उसी अभिव्यक्ति के विपरीत हो सकता है मूल्यांकन किया है।

यह अपने आप नहीं लगता है कि यह एक समस्या पैदा करेगा - यह मानते हुए कि ऑपरेशन किया जा रहा है, मान -1 को मेमोरी लोकेशन में स्टोर कर रहा है। लेकिन यह कहने के लिए भी कुछ नहीं है कि संकलक निर्देशों का एक अलग सेट में अनुकूलन नहीं कर सकता है जिसका समान प्रभाव है, लेकिन जो विफल हो सकता है यदि ऑपरेशन उसी मेमोरी स्थान पर किसी अन्य ऑपरेशन के साथ इंटरलेय किया गया था।

उदाहरण के लिए, कल्पना करें कि मेमोरी को शून्य करने के लिए यह अधिक कुशल था, फिर मूल्य -1 को लोड करने की तुलना में इसे घटाएं। फिर: यह।

f(i=-1, i=-1)

बना सकता है:

clear i
clear i
decr i
decr i

अब मैं -2 हूं।

यह शायद एक फर्जी उदाहरण है, लेकिन यह संभव है।


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

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

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

1
+ 1e + 6 (ठीक, +1) इस बात के लिए कि संकलित कोड हमेशा वह नहीं होता जिसकी आप अपेक्षा करते हैं। जब आप नियमों का पालन नहीं करते हैं तो ऑप्टिमाइज़र इस प्रकार के कर्व्स को आप पर फेंकने में बहुत अच्छे होते हैं : P
कोरी

3
आर्म प्रोसेसर पर एक लोड 32 बिट 4 निर्देश तक ले सकता है: यह load 8bit immediate and shift4 बार तक होता है। आमतौर पर संकलक अप्रत्यक्ष रूप से इससे बचने के लिए एक अंक तालिका बनाने के लिए अप्रत्यक्ष रूप से संबोधित करेंगे। (-1 निर्देश में किया जा सकता है, लेकिन एक और उदाहरण चुना जा सकता है)।
ctrl-alt-delor

208

सबसे पहले, "अदिश वस्तु" एक तरह एक प्रकार का मतलब है int, floatया एक सूचक (देखें क्या C ++ एक अदिश वस्तु है? )।


दूसरा, यह अधिक स्पष्ट लग सकता है

f(++i, ++i);

अपरिभाषित व्यवहार होगा। परंतु

f(i = -1, i = -1);

कम स्पष्ट है।

थोड़ा अलग उदाहरण:

int i;
f(i = 1, i = -1);
std::cout << i << "\n";

"अंतिम" i = 1, या क्या असाइनमेंट हुआ i = -1? यह मानक में परिभाषित नहीं है। वास्तव में, इसका मतलब iयह हो सकता है 5(देखें कि यह कैसे मामला हो सकता है) के लिए पूरी तरह से प्रशंसनीय स्पष्टीकरण के लिए हार्मिक का उत्तर देखें)। या आप प्रोग्राम सेगफॉल्ट कर सकते हैं। या अपनी हार्ड ड्राइव को पुन: स्वरूपित करें।

लेकिन अब आप पूछते हैं: "मेरे उदाहरण के बारे में क्या? मैंने -1दोनों असाइनमेंट के लिए समान मूल्य ( ) का उपयोग किया है । संभवतः इसके बारे में स्पष्ट नहीं हो सकता है?"

आप सही हैं ... सी ++ मानक समिति ने जिस तरह से इसे वर्णित किया है, उसे छोड़कर।

यदि एक स्केलर ऑब्जेक्ट पर एक साइड इफेक्ट एक ही स्केलर ऑब्जेक्ट पर एक और साइड इफेक्ट के सापेक्ष आरोपित नहीं किया जाता है, तो व्यवहार अपरिभाषित है।

वे आपके विशेष मामले के लिए एक विशेष अपवाद बना सकते थे , लेकिन उन्होंने ऐसा नहीं किया। (और वे क्यों चाहिए? क्या उपयोग होगा जो संभवतः संभवतः होगा?) इसलिए, iअभी भी हो सकता है 5। या आपकी हार्ड ड्राइव खाली हो सकती है। इस प्रकार आपके प्रश्न का उत्तर है:

यह अपरिभाषित व्यवहार है क्योंकि यह परिभाषित नहीं है कि व्यवहार क्या है।

(यह जोर देने योग्य है क्योंकि कई प्रोग्रामर सोचते हैं कि "अपरिभाषित" का अर्थ है "यादृच्छिक", या "अप्रत्याशित"। इसका मतलब यह नहीं है कि यह मानक द्वारा परिभाषित नहीं है। व्यवहार 100% सुसंगत हो सकता है, और अभी भी अपरिभाषित हो सकता है।)

क्या यह परिभाषित व्यवहार हो सकता था? हाँ। क्या इसे परिभाषित किया गया था? नहीं। इसलिए, यह "अपरिभाषित" है।

उस ने कहा, "अपरिभाषित" का मतलब यह नहीं है कि एक कंपाइलर आपकी हार्ड ड्राइव को प्रारूपित करेगा ... इसका मतलब है कि यह हो सकता है और यह अभी भी एक मानक-अनुपालन कंपाइलर होगा। वास्तव में, मुझे यकीन है कि जी ++, क्लैंग, और एमएसवीसी सभी वही करेंगे जो आपको उम्मीद थी। वे सिर्फ "करने के लिए" नहीं होगा।


एक अलग सवाल यह हो सकता है कि C ++ मानकों की समिति ने इस साइड-इफ़ेक्ट को अप्रभावित बनाने के लिए क्यों चुना? । उस उत्तर में इतिहास और समिति की राय शामिल होगी। या इस साइड-इफ़ेक्ट के C C ++ में न होने के बारे में क्या अच्छा है? , जो किसी भी औचित्य की अनुमति देता है, चाहे वह मानक समिति का वास्तविक तर्क था या नहीं। आप उन सवालों को यहाँ, या programmers.stackexchange.com पर पूछ सकते हैं।


9
@hvd, हाँ, वास्तव में मुझे पता है कि यदि आप -Wsequence-pointg ++ के लिए सक्षम करते हैं, तो यह आपको चेतावनी देगा।
पॉल ड्रेपर

47
"मुझे यकीन है कि जी ++, क्लैंग, और एमएसवीसी सभी आप की उम्मीद करेंगे" मुझे एक आधुनिक संकलक पर भरोसा नहीं होगा। वे दुष्ट हैं। उदाहरण के लिए वे पहचान सकते हैं कि यह अपरिभाषित व्यवहार है और मान लें कि यह कोड अनुपलब्ध है। यदि वे आज ऐसा नहीं करते हैं, तो वे कल ऐसा कर सकते हैं। कोई भी यूबी एक टिक टाइम बम है।
कोडइन्चोस

8
@BlacklightShining "आपका जवाब बुरा है क्योंकि यह अच्छा नहीं है" बहुत उपयोगी प्रतिक्रिया नहीं है, है ना?
विंसेंट वैन डेर वेले

13
@ याकूब जार्विस के पास अपरिभाषित व्यवहार के कारण दूर से सही कोड उत्पन्न करने का कोई दायित्व नहीं है। टी.आई.टी. यह भी मान सकता है कि इस कोड को कभी भी नहीं बुलाया गया है और इस प्रकार पूरी बात को एक झपकी से बदल दिया गया है (ध्यान दें कि कंपाइलर वास्तव में यूबी के सामने ऐसी धारणा बनाते हैं)। इसलिए मैं कहूंगा कि इस तरह की बग रिपोर्ट की सही प्रतिक्रिया केवल "बंद, काम कर सकती है"
ग्रिजली

7
@SchighSchagh कभी-कभी शब्दों की एक रीफ़्रेशिंग (जो केवल सतह पर दिखाई देती है, वह एक तुच्छ उत्तर है) जो लोगों को चाहिए। अधिकांश तकनीकी विशिष्टताओं के लिए नए लोगों को लगता है undefined behaviorइसका मतलब है something random will happen, जिस स्थिति समय के सबसे अधिक से दूर है।
इज़्काता

27

नियमों का अपवाद न करने का एक व्यावहारिक कारण सिर्फ इसलिए कि दो मूल्य समान हैं:

// config.h
#define VALUEA  1

// defaults.h
#define VALUEB  1

// prog.cpp
f(i = VALUEA, i = VALUEB);

इस मामले पर गौर करें।

अब, कुछ महीनों बाद, जरूरत बदल जाती है

 #define VALUEB 2

लगातार हानिरहित, है ना? और फिर भी अचानक prog.cpp अब और संकलन नहीं करेगा। फिर भी, हमें लगता है कि संकलन को शाब्दिक मूल्य पर निर्भर नहीं होना चाहिए।

नीचे पंक्ति: नियम का कोई अपवाद नहीं है क्योंकि यह सफल संकलन को स्थिरांक के मूल्य (बल्कि प्रकार) पर निर्भर करेगा।

संपादित करें

@HeartWare ने बताया कि A DIV Bकुछ भाषाओं में फॉर्म की निरंतर अभिव्यक्ति की अनुमति नहीं है, जब B0 है, और असफल होने के लिए संकलन का कारण बनता है। इसलिए किसी स्थिरांक के बदलने से किसी अन्य स्थान पर संकलन त्रुटियां हो सकती हैं। जो, IMHO, दुर्भाग्यपूर्ण है। लेकिन अपरिहार्य के लिए ऐसी चीजों को प्रतिबंधित करना निश्चित रूप से अच्छा है।


ज़रूर, लेकिन उदाहरण पूर्णांक शाब्दिक का उपयोग करता है। आपके f(i = VALUEA, i = VALUEB);पास निश्चित रूप से अपरिभाषित व्यवहार की क्षमता है। मुझे आशा है कि आप वास्तव में पहचानकर्ताओं के पीछे मूल्यों के खिलाफ कोडिंग नहीं कर रहे हैं।
वुल्फ

3
@Wold लेकिन संकलक प्रीप्रोसेसर मैक्रोज़ नहीं देखता है। और यहां तक ​​कि अगर यह ऐसा नहीं था, तो किसी भी प्रोग्रामिंग भाषा में एक उदाहरण खोजना मुश्किल है, जहां एक स्रोत कोड तब तक संकलित करता है जब तक कि कोई 1 से 2 तक कुछ अंतर स्थिर नहीं बदलता है। यह बस अस्वीकार्य और अनपेक्षित है, जबकि आप यहां बहुत अच्छा स्पष्टीकरण देखते हैं क्यों कि कोड समान मूल्यों के साथ भी टूट गया है।
ingo

हां, संकलन मैक्रोज़ नहीं देखता है। लेकिन, क्या यह सवाल था?
वुल्फ

1
आपका उत्तर बिंदु को याद कर रहा है, हार्मोनिक के उत्तर और उस पर ओपी की टिप्पणी पढ़ें ।
वुल्फ

1
यह कर सकता था SomeProcedure(A, B, B DIV (2-A))। वैसे भी, यदि भाषा में कहा गया है कि CONST का संकलन समय पर पूरी तरह से मूल्यांकन किया जाना चाहिए, तो, निश्चित रूप से, मेरा दावा उस मामले के लिए मान्य नहीं है। चूंकि यह किसी तरह संकलन और क्रम के अंतर को धुंधला कर देता है। हम लिखेंगे तो क्या यह भी ध्यान देगा CONST C = X(2-A); FUNCTION X:INTEGER(CONST Y:INTEGER) = B/Y; ?? या कार्यों की अनुमति नहीं है?
इंगो

12

भ्रम यह है कि एक स्थानीय चर में एक स्थिर मूल्य को संग्रहीत करना हर वास्तुकला पर एक परमाणु निर्देश नहीं है जिसे C को चलाने के लिए डिज़ाइन किया गया है। प्रोसेसर इस मामले में संकलक से अधिक मामलों पर कोड चलाता है। उदाहरण के लिए, एआरएम पर जहां प्रत्येक निर्देश पूर्ण 32 बिट्स को निरंतर नहीं ले जा सकता है, एक चर में एक इंट को संग्रहीत करने के लिए अधिक निर्देश की आवश्यकता होती है। इस छद्म कोड के साथ उदाहरण जहां आप एक बार में केवल 8 बिट्स स्टोर कर सकते हैं और 32 बिट्स रजिस्टर में काम करना चाहिए, मैं एक इंट्यूशन है:

reg = 0xFF; // first instruction
reg |= 0xFF00; // second
reg |= 0xFF0000; // third
reg |= 0xFF000000; // fourth
i = reg; // last

आप कल्पना कर सकते हैं कि यदि कंपाइलर इसे ऑप्टिमाइज़ करना चाहता है तो एक ही सीक्वेंस को दो बार इंटरलेक्ट कर सकता है, और आपको नहीं पता कि किस वैल्यू में लिखा होगा; और मान लें कि वह बहुत चालाक नहीं है:

reg = 0xFF;
reg |= 0xFF00;
reg |= 0xFF0000;
reg = 0xFF;
reg |= 0xFF000000;
i = reg; // writes 0xFF0000FF == -16776961
reg |= 0xFF00;
reg |= 0xFF0000;
reg |= 0xFF000000;
i = reg; // writes 0xFFFFFFFF == -1

हालाँकि मेरे परीक्षणों में यह मानने के लिए पर्याप्त है कि एक ही मूल्य का उपयोग दो बार किया जाता है और इसे एक बार उत्पन्न करता है और कुछ भी अजीब नहीं होता है। मुझे -1, -1 मिलता है, लेकिन मेरा उदाहरण अभी भी मान्य है क्योंकि यह विचार करना महत्वपूर्ण है कि एक स्थिर भी उतना स्पष्ट नहीं हो सकता है जितना लगता है।


मुझे लगता है कि एआरएम पर कंपाइलर बस एक टेबल से निरंतर लोड होगा। आप जो वर्णन करते हैं वह MIPS की तरह लगता है।
ACH

1
@AndreyChernyakhovskiy Yep, लेकिन एक मामले में जब यह बस नहीं है -1(कि कंपाइलर ने कहीं स्टोर किया है), लेकिन यह बल्कि है 3^81 mod 2^32, लेकिन स्थिर है, तो कंपाइलर ठीक वही कर सकता है जो यहां किया गया है, और कुछ लेट ऑफ ऑमीटिमाइजेशन में, मेरे इंटरलाइव कॉल सिक्वेंस इंतजार करने से बचें।
यो '

@tohecz, हाँ, मैंने इसे पहले ही देख लिया है। वास्तव में, संकलक बहुत अधिक स्मार्ट है एक तालिका से हर निरंतर लोड करने के लिए। वैसे भी, यह दो स्थिरांक की गणना करने के लिए एक ही रजिस्टर का उपयोग नहीं करेगा। यह निश्चित रूप से परिभाषित व्यवहार को 'अपरिभाषित' करेगा।
ach

@AndreyChernyakhovskiy लेकिन आप शायद "दुनिया में हर C ++ कंपाइलर प्रोग्रामर" नहीं हैं। याद रखें कि केवल गणना के लिए 3 छोटे रजिस्टर के साथ मशीनें उपलब्ध हैं।
यो '

@tohecz, उदाहरण पर विचार करें f(i = A, j = B)जहां iऔर jदो अलग-अलग वस्तुएं हैं। इस उदाहरण का कोई UB नहीं है। मशीन 3 कम रजिस्टरों होने संकलक के दो मानों मिश्रण करने के लिए कोई बहाना नहीं है Aऔर Bक्योंकि यह कार्यक्रम अर्थ विज्ञान टूट जाएगा, एक ही रजिस्टर में (के रूप में @ davidf के जवाब में दिखाया गया है)।
ach

11

व्यवहार को आमतौर पर अपरिभाषित के रूप में निर्दिष्ट किया जाता है अगर कुछ बोधगम्य कारण है कि एक संकलक जो "सहायक" होने की कोशिश कर रहा था, वह कुछ ऐसा कर सकता है जो पूरी तरह से अप्रत्याशित व्यवहार का कारण होगा।

उस स्थिति में जहां एक चर को कई बार लिखा जाता है, जिसमें यह सुनिश्चित करने के लिए कि कुछ अलग-अलग समय पर होता है, कुछ प्रकार के हार्डवेयर दोहरे पोर्ट मेमोरी का उपयोग करके एक साथ कई "स्टोर" संचालन को अलग-अलग पते पर करने की अनुमति दे सकते हैं। हालांकि, कुछ दोहरे पोर्ट की यादें स्पष्ट रूप से उस परिदृश्य से मना करती हैं जहां दो स्टोर एक ही पते पर एक साथ टकराते हैं, भले ही लिखे गए मान मेल हों या नहीं।। यदि ऐसी मशीन के लिए संकलक एक ही चर को लिखने के लिए दो बिना सोचे समझे प्रयास करता है, तो वह या तो यह संकलन करने से इनकार कर सकता है या सुनिश्चित कर सकता है कि दोनों लेखन एक साथ निर्धारित नहीं हो सकते। लेकिन अगर एक या दोनों एक्सेस पॉइंटर या संदर्भ के माध्यम से होते हैं, तो कंपाइलर हमेशा यह बताने में सक्षम नहीं हो सकता है कि क्या दोनों राइट्स एक ही स्टोरेज लोकेशन को हिट कर सकते हैं। उस स्थिति में, यह एक साथ लिखने का समय निर्धारित कर सकता है, जिससे एक्सेस प्रयास पर एक हार्डवेयर जाल हो सकता है।

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

uint8_t v;  // Global

void hey(uint8_t *p)
{
  moo(v=5, (*p)=6);
  zoo(v);
  zoo(v);
}

यदि कंपाइलर "moo" को कॉल इन-लाइन्स करता है और यह बता सकता है कि यह "v" को संशोधित नहीं करता है, तो यह 5 से v स्टोर कर सकता है, फिर 6 से * p स्टोर करें, फिर 5 से "zoo" पास करें, और फिर v की सामग्री को "चिड़ियाघर" पास करें। यदि "चिड़ियाघर" "v" को संशोधित नहीं करता है, तो कोई रास्ता नहीं होना चाहिए दो कॉल को अलग-अलग मानों को पारित किया जाना चाहिए, लेकिन यह आसानी से वैसे भी हो सकता है। दूसरी ओर, ऐसे मामलों में जहां दोनों स्टोर एक ही मूल्य लिखेंगे, ऐसी अजीबता नहीं हो सकती है और अधिकांश प्लेटफार्मों पर कार्यान्वयन के लिए कुछ भी अजीब करने का कोई समझदार कारण नहीं होगा। दुर्भाग्य से, कुछ संकलक लेखकों को "क्योंकि मानक इसे अनुमति देता है" से परे मूर्ख व्यवहार के लिए किसी भी बहाने की आवश्यकता नहीं है, इसलिए भी वे मामले सुरक्षित नहीं हैं।


9

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

यह देखते हुए कि अभिव्यक्ति को एक अपरिभाषित व्यवहार के साथ निर्दिष्ट किया गया है, दुर्भावनापूर्ण कंपाइलर एक अनुचित छवि प्रदर्शित कर सकता है जब आप मूल्यांकन करते हैं f(i = -1, i = -1)और निष्पादन को निरस्त करते हैं - और फिर भी पूरी तरह से सही माना जाता है। सौभाग्य से, कोई संकलक मुझे ऐसा करने के बारे में पता नहीं है।


8

यह मुझे लगता है कि फ़ंक्शन तर्क अभिव्यक्ति की अनुक्रमण से संबंधित एकमात्र नियम यहाँ है:

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

यह तर्क अभिव्यक्तियों के बीच अनुक्रमण को परिभाषित नहीं करता है, इसलिए हम इस मामले में समाप्त होते हैं:

1) यदि एक स्केलर ऑब्जेक्ट पर एक साइड इफेक्ट एक ही स्केलर ऑब्जेक्ट पर एक और साइड इफेक्ट के सापेक्ष आरोपित नहीं किया जाता है, तो व्यवहार अपरिभाषित है।

व्यवहार में, अधिकांश संकलकों पर, आपके द्वारा उद्धृत उदाहरण ठीक चलेगा (जैसा कि "आपकी हार्ड डिस्क को मिटाने" और अन्य सैद्धांतिक अपरिभाषित व्यवहार परिणामों के विपरीत)।
हालाँकि, यह एक दायित्व है, क्योंकि यह विशिष्ट संकलक व्यवहार पर निर्भर करता है, भले ही दो निर्दिष्ट मान समान हों। इसके अलावा, जाहिर है, यदि आपने विभिन्न मूल्यों को निर्दिष्ट करने की कोशिश की, तो परिणाम "सही मायने में" अपरिभाषित होंगे:

void f(int l, int r) {
    return l < -1;
}
auto b = f(i = -1, i = -2);
if (b) {
    formatDisk();
}

8

C ++ 17 कड़े मूल्यांकन नियमों को परिभाषित करता है। विशेष रूप से, यह फ़ंक्शन तर्क (हालांकि अनिर्दिष्ट क्रम में) अनुक्रम करता है।

N5659 §4.6:15
मूल्यांकन एक और बी धुंधलेपन से अनुक्रम कर रहे हैं जब या तो एक से पहले अनुक्रम है बी या बी से पहले अनुक्रम है एक है, लेकिन यह जो अनिर्दिष्ट है। [ नोट : अनिश्चित रूप से अनुक्रमित मूल्यांकन ओवरलैप नहीं हो सकता है, लेकिन या तो पहले निष्पादित किया जा सकता है। - अंतिम नोट ]

N5659 § 8.2.2:5
प्रत्येक संबंधित मूल्य संगणना और साइड इफेक्ट सहित एक पैरामीटर का प्रारंभ, अनिश्चित रूप से किसी अन्य पैरामीटर के संबंध में अनुक्रमित है।

यह कुछ मामलों की अनुमति देता है जो पहले यूबी होंगे:

f(i = -1, i = -1); // value of i is -1
f(i = -1, i = -2); // value of i is either -1 or -2, but not specified which one

2
C ++ 17 के लिए इस अपडेट को जोड़ने के लिए धन्यवाद , इसलिए मुझे नहीं करना पड़ा। ;)
यक - एडम नेवरामॉन्ट

बहुत बढ़िया, इस उत्तर के लिए बहुत बहुत धन्यवाद। थोड़ा अनुवर्ती: यदि fहस्ताक्षर थे f(int a, int b), तो क्या C ++ 17 इसकी गारंटी देता है a == -1और b == -2यदि दूसरे मामले में कहा जाता है?
निकु स्तिर्का

हाँ। हम पैरामीटर हैं तो aऔर b, तो या तो i-then- a-1 के लिए शुरू कर रहे हैं, बाद में i-then- bचारों ओर जिस तरह -2 के लिए शुरू कर रहे हैं, या। दोनों मामलों में, हम साथ समाप्त करते हैं a == -1और b == -2। कम से कम यह है कि मैं कैसे पढ़ता हूं " प्रत्येक संबंधित मूल्य संगणना और साइड इफेक्ट सहित एक पैरामीटर का प्रारंभ, अनिश्चित रूप से किसी अन्य पैरामीटर के संबंध में अनुक्रमित है "।
एलेक्स एनडी

मुझे लगता है कि सी में हमेशा से ऐसा ही रहा है।
फ़ूज

5

असाइनमेंट ऑपरेटर को अधिभारित किया जा सकता है, जिस स्थिति में ऑर्डर मायने रख सकता है:

struct A {
    bool first;
    A () : first (false) {
    }
    const A & operator = (int i) {
        first = !first;
        return * this;
    }
};

void f (A a1, A a2) {
    // ...
}


// ...
A i;
f (i = -1, i = -1);   // the argument evaluated first has ax.first == true

1
यह सच है, लेकिन सवाल अदिश प्रकारों के बारे में था , जिन्हें अन्य लोगों ने इंगित किया है कि अनिवार्य रूप से इंट परिवार, फ्लोट परिवार, और संकेत।
निकु स्तिर्का

इस मामले में वास्तविक समस्या यह है कि असाइनमेंट ऑपरेटर स्टेटफुल है, इसलिए चर के नियमित हेरफेर से भी इस तरह के मुद्दों का खतरा है।
AJMansfield

2

यह सिर्फ "मुझे यकीन नहीं है कि क्या" अदिश वस्तु "एक अंतर या एक नाव की तरह कुछ के अलावा मतलब हो सकता है" का जवाब दे रहा है।

मैं "स्केलर ऑब्जेक्ट" की व्याख्या "स्केलर टाइप ऑब्जेक्ट" के संक्षिप्त नाम के रूप में, या "स्केलर टाइप वेरिएबल" के रूप में करूंगा। फिर, pointer, enum(स्थिर) अदिश प्रकार के हैं।

यह स्केलर टाइप्स का एक MSDN आलेख है ।


यह "लिंक केवल उत्तर" की तरह थोड़ा सा पढ़ता है। क्या आप उस लिंक से प्रासंगिक बिट्स को इस उत्तर में (एक ब्लॉकचोट में) कॉपी कर सकते हैं?
कोल जॉनसन

1
@ColeJohnson यह केवल लिंक का जवाब नहीं है। लिंक केवल आगे की व्याख्या के लिए है। मेरा जवाब "पॉइंटर", "एनम" है।
पेंग झांग

मैंने नहीं कहा कि आपका उत्तर केवल एक लिंक था । मैंने कहा कि यह "[एक] की तरह पढ़ता है" । मेरा सुझाव है कि आप पढ़ें कि हम सहायता अनुभाग में केवल उत्तर क्यों नहीं चाहते हैं। कारण, यदि Microsoft अपने URL को अपनी साइट में अपडेट करता है, तो वह लिंक टूट जाता है।
कोल जॉनसन

1

असल में, इस तथ्य पर निर्भर न होने का एक कारण यह है कि संकलक यह जांच करेगा कि iएक ही मूल्य के साथ दो बार असाइन किया गया है, ताकि इसे एकल असाइनमेंट के साथ बदलना संभव हो। अगर हमारे पास कुछ भाव हैं तो क्या होगा?

void g(int a, int b, int c, int n) {
    int i;
    // hey, compiler has to prove Fermat's theorem now!
    f(i = 1, i = (ipow(a, n) + ipow(b, n) == ipow(c, n)));
}

1
Fermat के प्रमेय को साबित करने की आवश्यकता नहीं है: बस असाइन 1करें i। या तो दोनों तर्क देते हैं 1और यह "सही" बात करता है, या तर्क अलग-अलग मूल्य प्रदान करते हैं और यह अपरिभाषित व्यवहार है इसलिए हमारी पसंद अभी भी अनुमति है।
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.