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




language-lawyer undefined-behavior (8)

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

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

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

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

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

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

क्या कोई स्पष्टीकरण दे सकता है?

अद्यतन करें

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

सारांश

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

किस कारण से

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

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

सी ++ मानक कहता है कि यह समझाने के संदर्भ में जवाब कुल मिलाकर बहुत अच्छा है। यह यूबी के कुछ संबंधित मामलों को भी संबोधित करता है जैसे कि 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) अनिश्चित व्यवहार के रूप में छोड़ दिया गया सवाल के लिए लाता है?"

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

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

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

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

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

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

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

अन्य जवाब

JohnB बताते हैं कि अगर हम ओवरलोडेड असाइनमेंट ऑपरेटर (केवल सादे स्केलर्स के बजाए) पर विचार करते हैं, तो हम भी परेशानी में भाग सकते हैं।


असाइनमेंट ऑपरेटर को ओवरलोड किया जा सकता है, इस मामले में ऑर्डर दे सकता है:

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

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

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

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

1) यदि स्केलर ऑब्जेक्ट पर एक दुष्प्रभाव एक ही स्केलर ऑब्जेक्ट पर किसी अन्य दुष्प्रभाव के सापेक्ष अपरिचित है, तो व्यवहार अपरिभाषित है।

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

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

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

यह देखते हुए कि अभिव्यक्ति को एक अनिर्धारित व्यवहार के रूप में निर्दिष्ट किया गया है, जब आप f(i = -1, i = -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)));
}

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

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 मिलता है लेकिन मेरा उदाहरण अभी भी मान्य है क्योंकि यह मानना ​​महत्वपूर्ण है कि एक स्थिर भी उतना स्पष्ट नहीं हो सकता जितना लगता है।


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

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

यह स्केलर प्रकारों का एक एमएसडीएन लेख है।


सबसे पहले, "स्केलर ऑब्जेक्ट" का अर्थ एक 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 हो सकता हूं (इस चोटी के मामले के लिए पूरी तरह से व्यावहारिक स्पष्टीकरण के लिए हार्मिक का जवाब देखें)। या आप प्रोग्राम segfault सकता है। या अपनी हार्ड ड्राइव को दोबारा सुधारें।

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

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

यदि एक स्केलर ऑब्जेक्ट पर एक दुष्प्रभाव एक ही स्केलर ऑब्जेक्ट पर किसी अन्य दुष्प्रभाव के सापेक्ष अपरिचित है, तो व्यवहार अपरिभाषित है।

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

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

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

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

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

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


सी ++ 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






undefined-behavior