क्या अपरिभाषित व्यवहार वाली शाखाओं को अप्राप्य माना जा सकता है और उन्हें मृत कोड के रूप में अनुकूलित किया जा सकता है?


88

निम्नलिखित कथन पर विचार करें:

*((char*)NULL) = 0; //undefined behavior

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

यदि उपयोगकर्ता कभी भी संख्या में प्रवेश नहीं करता है तो क्या निम्न कार्यक्रम को अच्छी तरह से परिभाषित किया जाएगा 3?

while (true) {
 int num = ReadNumberFromConsole();
 if (num == 3)
  *((char*)NULL) = 0; //undefined behavior
}

या यह पूरी तरह से अपरिभाषित व्यवहार है कोई फर्क नहीं पड़ता कि उपयोगकर्ता क्या प्रवेश करता है?

इसके अलावा, क्या कंपाइलर मान सकता है कि अपरिभाषित व्यवहार को रनटाइम पर कभी निष्पादित नहीं किया जाएगा? यह समय में पीछे की ओर तर्क के लिए अनुमति देगा:

int num = ReadNumberFromConsole();

if (num == 3) {
 PrintToConsole(num);
 *((char*)NULL) = 0; //undefined behavior
}

यहां, कंपाइलर यह कारण दे सकता है कि अगर num == 3हम हमेशा अपरिभाषित व्यवहार को लागू करेंगे। इसलिए, यह मामला असंभव होना चाहिए और संख्या को मुद्रित करने की आवश्यकता नहीं है। पूरे ifविवरण को अनुकूलित किया जा सकता है। क्या मानक के अनुसार इस तरह के पिछड़े तर्क की अनुमति है?


19
कभी-कभी मुझे आश्चर्य होता है कि बहुत सारे प्रतिनिधि वाले उपयोगकर्ता प्रश्नों पर अधिक उठाव करते हैं क्योंकि "ओह, उनके पास बहुत सारे प्रतिनिधि हैं, यह एक अच्छा प्रश्न होना चाहिए" ... लेकिन इस मामले में मैंने प्रश्न पढ़ा और सोचा "वाह, यह बहुत अच्छा है “इससे पहले कि मैं भी पूछने वाले को देखता।
टर्बुलेंसेटू

4
मुझे लगता है कि उस समय जब अपरिभाषित व्यवहार उभर, अनिर्धारित रहता है।
एरोरिका

6
C ++ मानक स्पष्ट रूप से कहता है कि किसी भी बिंदु पर अपरिभाषित व्यवहार के साथ एक निष्पादन पथ पूरी तरह से अपरिभाषित है। मैं इसकी व्याख्या यह भी कहूंगा कि पथ पर अपरिभाषित व्यवहार वाला कोई भी कार्यक्रम पूरी तरह से अपरिभाषित है (जिसमें अन्य भागों पर उचित परिणाम शामिल हैं, लेकिन इसकी गारंटी नहीं है)। कंपाइलर्स आपके प्रोग्राम को संशोधित करने के लिए अपरिभाषित व्यवहार का उपयोग करने के लिए स्वतंत्र हैं। blog.llvm.org/2011/05/what-every-c-programmer-should-know.html में कुछ अच्छे उदाहरण हैं।
जेन्स

4
@ जेन्स: इसका वास्तव में मतलब सिर्फ क्रियान्वयन पथ है। और तुम मुसीबत में पड़ जाते हो const int i = 0; if (i) 5/i;
MSalters

1
सामान्य रूप से संकलक साबित नहीं कर सकता है कि PrintToConsoleयह कॉल नहीं करता है std::exitइसलिए इसे कॉल करना होगा।
MSalters

जवाबों:


65

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

न तो। पहली स्थिति बहुत मजबूत है और दूसरी बहुत कमजोर है।

ऑब्जेक्ट एक्सेस कभी-कभी अनुक्रमित होती है, लेकिन मानक समय के बाहर कार्यक्रम के व्यवहार का वर्णन करता है। दानविल पहले ही उद्धृत:

यदि इस तरह के किसी भी निष्पादन में एक अपरिभाषित ऑपरेशन होता है, तो यह अंतर्राष्ट्रीय मानक उस इनपुट के साथ उस प्रोग्राम को कार्यान्वित करने वाले कार्यान्वयन पर कोई आवश्यकता नहीं रखता है (पहले अपरिभाषित ऑपरेशन से पहले संचालन के संबंध में भी नहीं)

इसकी व्याख्या की जा सकती है:

यदि कार्यक्रम का निष्पादन अपरिभाषित व्यवहार देता है, तो पूरे कार्यक्रम में अपरिभाषित व्यवहार होता है।

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

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

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

आप इसके बारे में सोच सकते हैं, "यूबी के पास एक टाइम मशीन है"।

विशेष रूप से आपके उदाहरणों का जवाब देने के लिए:

  • यदि 3 को पढ़ा जाए तो व्यवहार केवल अपरिभाषित है।
  • कंपाइलर कोड को मृत के रूप में समाप्त कर सकते हैं यदि एक बुनियादी ब्लॉक में अपरिभाषित होना निश्चित है। उन्हें उन मामलों में अनुमति दी जाती है (मैं अनुमान लगा रहा हूं) जो एक बुनियादी ब्लॉक नहीं हैं, लेकिन जहां सभी शाखाएं यूबी की ओर ले जाती हैं। यह उदाहरण एक उम्मीदवार नहीं PrintToConsole(3)है जब तक कि किसी तरह से वापस जाना सुनिश्चित न हो। यह एक अपवाद या जो कुछ भी फेंक सकता है।

आपके दूसरे के लिए एक समान उदाहरण gcc विकल्प है -fdelete-null-pointer-checks, जो इस तरह कोड ले सकता है (मैंने इस विशिष्ट उदाहरण की जांच नहीं की है, इसे सामान्य विचार का चित्रण मानें):

void foo(int *p) {
    if (p) *p = 3;
    std::cout << *p << '\n';
}

और इसे इसमें बदलें:

*p = 3;
std::cout << "3\n";

क्यों? क्योंकि यदि pअशक्त है, तो कोड में यूबी वैसे भी है, इसलिए कंपाइलर मान सकता है कि यह शून्य नहीं है और तदनुसार अनुकूलित करें। लाइनक्स कर्नेल इस पर फंस गया ( https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2009-1897 ) अनिवार्य रूप से क्योंकि यह एक मोड में संचालित होता है, जहां अशक्त सूचक को निष्क्रिय करने की आवश्यकता नहीं है। यूबी हो, यह एक परिभाषित हार्डवेयर अपवाद में परिणाम की उम्मीद है जिसे कर्नेल संभाल सकता है। जब अनुकूलन सक्षम हो जाता है, तो gcc को -fno-delete-null-pointer-checksपरे-मानक गारंटी प्रदान करने के लिए उपयोग की आवश्यकता होती है ।

PS प्रश्न का व्यावहारिक उत्तर "जब अपरिभाषित व्यवहार हड़ताल करता है?" "10 मिनट पहले आप दिन के लिए निकलने की योजना बना रहे थे"।


4
दरअसल, अतीत में इसके कारण कुछ सुरक्षा मुद्दे थे। विशेष रूप से, किसी भी तथ्य के बाद के अतिप्रवाह चेक को इसके कारण दूर अनुकूलित होने का खतरा है। उदाहरण के लिए void can_add(int x) { if (x + 100 < x) complain(); }, दूर पूरी तरह से अनुकूलित किया जा सकता है क्योंकि अगर x+100 नहीं करता है ' अतिप्रवाह कुछ नहीं होता है, और यदि x+100 करता है अतिप्रवाह, बात यह है कि मानक के अनुसार यूबी, इसलिए कुछ भी हो सकता है होता है।
fgp

3
@ एफजीपी: सही, यह एक अनुकूलन है जो लोग इस पर शिकायत करते हैं कि अगर वे इस पर यात्रा करते हैं, क्योंकि ऐसा महसूस होता है कि कंपाइलर जानबूझकर आपको दंडित करने के लिए आपके कोड को तोड़ रहा है। "मैं इसे इस तरह क्यों लिखता अगर मैं चाहता कि आप इसे हटा दें!" ;; लेकिन मुझे लगता है कि कभी-कभी यह आशावादी के लिए उपयोगी होता है जब बड़े अंकगणितीय अभिव्यक्तियों में हेरफेर किया जाता है, यह मानने के लिए कि कोई अतिप्रवाह नहीं है और कुछ भी महंगी से बचें जो केवल उन मामलों में आवश्यक होंगे।
स्टीव जेसोप

2
क्या यह कहना सही होगा कि यदि उपयोगकर्ता 3 में प्रवेश नहीं करता है, तो कार्यक्रम अपरिभाषित नहीं है, लेकिन यदि वह निष्पादन के दौरान 3 में प्रवेश करता है, तो पूरा निष्पादन अपरिभाषित हो जाता है? जैसे ही यह 100% निश्चित है कि कार्यक्रम अपरिभाषित व्यवहार को आमंत्रित करेगा (और इससे जल्दी नहीं) व्यवहार कुछ भी बनने की अनुमति देता है। क्या मेरा ये कथन 100% सही है?
usr

3
@usr: मेरा मानना ​​है कि यह सही है, हाँ। आपके विशेष उदाहरण के साथ (और संसाधित किए जा रहे डेटा की अनिवार्यता के बारे में कुछ धारणाएं बनाते हुए) मुझे लगता है कि 3यदि कोई ऐसा करना चाहता था, तो सिद्धांत रूप में एक कार्यान्वयन बफर STDIN में आगे देख सकता है, और जैसे ही उसने देखा, दिन के लिए घर से दूर पैक करना। भेजे।
स्टीव जेसोप

3
आपके PS के लिए एक अतिरिक्त +1 (यदि मैं कर सकता था)
फ्रेड लार्सन

10

मानक 1.9 / 4 पर बताता है

[नोट: यह अंतर्राष्ट्रीय मानक उन कार्यक्रमों के व्यवहार पर कोई आवश्यकता नहीं लगाता है जिनमें अपरिभाषित व्यवहार होता है। - अंतिम नोट]

दिलचस्प बात शायद यह है कि "समाहित" का अर्थ क्या है। थोड़ा बाद में 1.9 / 5 पर यह बताता है:

हालाँकि, यदि इस तरह के किसी भी निष्पादन में एक अपरिभाषित ऑपरेशन होता है, तो यह अंतर्राष्ट्रीय मानक उस इनपुट के साथ उस प्रोग्राम को कार्यान्वित करने वाले कार्यान्वयन पर कोई आवश्यकता नहीं रखता है (पहले अपरिभाषित ऑपरेशन से पहले संचालन के संबंध में भी नहीं)

यहाँ यह विशेष रूप से "निष्पादन ... उस इनपुट के साथ" का उल्लेख करता है। मैं इसकी व्याख्या करता हूं कि एक संभावित शाखा में अपरिभाषित व्यवहार, जिसे अभी निष्पादित नहीं किया गया है, निष्पादन की वर्तमान शाखा को प्रभावित नहीं करता है।

एक अलग मुद्दा हालांकि कोड पीढ़ी के दौरान अपरिभाषित व्यवहार पर आधारित धारणाएं हैं। इसके बारे में अधिक जानकारी के लिए स्टीव जेसप का उत्तर देखें।


1
यदि सचमुच लिया जाता है, तो अस्तित्व में सभी वास्तविक कार्यक्रमों के लिए मौत की सजा है।
usr

6
मुझे नहीं लगता कि यूबीबी कोड के पहुंचने से पहले प्रकट हो सकता है। सवाल, जैसा कि मैंने इसे समझा था, अगर यूबी दिखाई दे सकता है अगर कोड भी नहीं पहुंचेगा। और निश्चित रूप से इसका उत्तर "नहीं" है।
sepp2k

अच्छी तरह से मानक 1.9 / 4 में इस बारे में स्पष्ट नहीं है, लेकिन 1.9 / 5 की व्याख्या संभवतः की जा सकती है जैसा आपने कहा था।
दानविल

1
नोट्स गैर-आदर्श हैं। 1.9 / 5 में 1.9 / 5 के नोट को ट्रम्प करता है
एमएसल्टर्स

5

एक शिक्षाप्रद उदाहरण है

int foo(int x)
{
    int a;
    if (x)
        return a;
    return 0;
}

वर्तमान GCC और वर्तमान Clang दोनों इसे (x86 पर) ऑप्टिमाइज़ करेंगे

xorl %eax,%eax
ret

क्योंकि वे अनुमान है कि xहमेशा शून्य है में यूबी से if (x)नियंत्रण पथ। GCC भी आप का उपयोग-अनैतिकता-मूल्य चेतावनी नहीं देगा! (क्योंकि उक्त तर्क को लागू करने वाला पास पास से पहले चलता है, जो असिंचित-मूल्य चेतावनी देता है)


1
दिलचस्प उदाहरण। यह बल्कि गंदा है कि अनुकूलन को सक्षम करने से चेतावनी छिप जाती है। यह भी प्रलेखित नहीं है - जीसीसी डॉक्स केवल यह कहता है कि अनुकूलन को सक्षम करने से अधिक चेतावनी उत्पन्न होती है
सालेस्के

@sleske यह बुरा है, मैं मानता हूं, लेकिन "अधिकार" प्राप्त करने के लिए असंवैधानिक मूल्य चेतावनियां बेहद मुश्किल हैं - उन्हें पूरी तरह से करना हॉल्टिंग समस्या के बराबर है, और प्रोग्रामर झूठे सकारात्मकता के लिए "अनावश्यक" परिवर्तनशील अनुकूलन जोड़ने के बारे में अजीब तरह से तर्कहीन हो जाते हैं, तो संकलक लेखक हवा एक बैरल पर जा रहा है। मैं जीसीसी पर हैक करता था और मुझे याद है कि हर कोई असिंचित-मूल्य-चेतावनी पास के साथ गड़बड़ करने से डरता था।
zwol

@zwol: मुझे आश्चर्य है कि इस तरह के डेड-कोड उन्मूलन से उत्पन्न "अनुकूलन" वास्तव में उपयोगी कोड को छोटा बनाता है, और प्रोग्रामर को कोड को बड़ा बनाने के लिए कितना समाप्त होता है (उदाहरण के लिए कोड को शुरू aकरने के लिए कोड जोड़कर सभी परिस्थितियों में भले ही एक असंगठित aसमारोह में पारित हो जाएगा कि फ़ंक्शन इसके साथ कुछ भी नहीं करेगा)?
सुपरकाट

@ सुपरफ़ुट मैं ~ 10 वर्षों में संकलक के काम में गहराई से शामिल नहीं हुआ है, और खिलौना उदाहरणों से अनुकूलन के बारे में तर्क करना लगभग असंभव है। इस प्रकार का अनुकूलन वास्तविक अनुप्रयोगों पर 2-5% समग्र कोड-आकार की कटौती के साथ जुड़ा हुआ है, अगर मुझे सही याद है।
zwol

1
इन चीजों के चलते @supercat 2-5% बहुत बड़ा है । मैंने देखा है कि लोग 0.1% तक पसीना बहाते हैं।
zwol

4

वर्तमान सी ++ काम करने का मसौदा 1.9.4 में कहता है कि

यह अंतर्राष्ट्रीय मानक उन कार्यक्रमों के व्यवहार पर कोई आवश्यकता नहीं लगाता है जिनमें fi ned व्यवहार शामिल हैं।

इसके आधार पर, मैं कहूंगा कि किसी भी निष्पादन पथ पर अपरिभाषित व्यवहार वाला कार्यक्रम अपने निष्पादन के हर समय कुछ भी कर सकता है।

अपरिभाषित व्यवहार पर दो बहुत अच्छे लेख हैं और आमतौर पर कंपाइलर क्या करते हैं:


1
इसका कोई अर्थ नही बन रहा है। समारोह int f(int x) { if (x > 0) return 100/x; else return 100; }निश्चित रूप से अपरिभाषित व्यवहार को कभी नहीं बुलाता है, भले ही 100/0अपरिभाषित हो।
fgp

1
@fgp मानक (विशेष रूप से 1.9 / 5) क्या कहता है, हालांकि, यह है कि यदि अपरिभाषित व्यवहार तक पहुंचा जा सकता है, तो यह तब तक कोई फर्क नहीं पड़ता जब यह पहुंच जाता है। उदाहरण के लिए, कुछ भी प्रिंट करने की गारंटी printf("Hello, World"); *((char*)NULL) = 0 नहीं है। यह अनुकूलन का अनुकूलन करता है, क्योंकि संकलक स्वतंत्र रूप से पुन: आदेश संचालन कर सकता है (निर्भरता की कमी के अधीन, निश्चित रूप से) यह जानता है कि अंततः पता चलेगा, बिना अपरिभाषित व्यवहार को ध्यान में रखे।
fgp

मैं कहूंगा कि आपके फ़ंक्शन के साथ एक कार्यक्रम में अपरिभाषित व्यवहार नहीं होता है, क्योंकि कोई इनपुट नहीं है जहां 100/0 का मूल्यांकन किया जाएगा।
जेन्स

1
बिल्कुल - तो क्या मायने रखता है कि क्या यूबी वास्तव में ट्रिगर किया जा सकता है या नहीं, यह नहीं कि क्या यह सैद्धांतिक रूप से ट्रिगर हो सकता है। या आप यह तर्क देने के लिए तैयार हैं कि int x,y; std::cin >> x >> y; std::cout << (x+y);यह कहने की अनुमति है कि "1 + 1 = 17", सिर्फ इसलिए कि कुछ इनपुट हैं जहां x+yओवरफ्लो (जो कि यूबी है क्योंकि intएक हस्ताक्षरित प्रकार है)।
fgp

औपचारिक रूप से, मैं कहूंगा कि कार्यक्रम में अपरिभाषित व्यवहार है क्योंकि इसमें इनपुट मौजूद हैं जो इसे ट्रिगर करते हैं। लेकिन आप सही हैं कि यह सी ++ के संदर्भ में कोई मतलब नहीं है, क्योंकि अपरिभाषित व्यवहार के बिना कार्यक्रम लिखना असंभव होगा। मुझे यह पसंद आएगा जब C ++ में अपरिभाषित व्यवहार कम था, लेकिन यह नहीं है कि भाषा कैसे काम करती है (और इसके लिए कुछ अच्छे कारण हैं, लेकिन वे मेरे दैनिक उपयोग की चिंता नहीं करते ...)।
जेन्स

3

शब्द "व्यवहार" का अर्थ है कि कुछ किया जा रहा है । एक राज्यकर्मी जिसे कभी निष्पादित नहीं किया जाता है वह "व्यवहार" नहीं है।

एक उदाहरण:

*ptr = 0;

क्या वह अपरिभाषित व्यवहार है? मान लीजिए कि हम ptr == nullptrकार्यक्रम के निष्पादन के दौरान कम से कम एक बार 100% निश्चित हैं । जवाब हां होना चाहिए।

इस बारे में क्या?

 if (ptr) *ptr = 0;

क्या यह अपरिभाषित है? ( ptr == nullptrकम से कम एक बार याद रखें ?) मुझे यकीन है कि आशा नहीं है, अन्यथा आप किसी भी उपयोगी कार्यक्रम को लिखने में सक्षम नहीं होंगे।

इस उत्तर को बनाने में किसी भी प्रकार की अनियमितता को नुकसान नहीं पहुंचाया गया।


3

अपरिभाषित व्यवहार तब टकराता है जब कार्यक्रम अपरिभाषित व्यवहार का कारण बनेगा चाहे जो भी हो। हालाँकि, आपने निम्न उदाहरण दिया।

int num = ReadNumberFromConsole();

if (num == 3) {
 PrintToConsole(num);
 *((char*)NULL) = 0; //undefined behavior
}

जब तक कंपाइलर की परिभाषा PrintToConsoleनहीं पता है , यह if (num == 3)सशर्त नहीं निकाल सकता है। मान लेते हैं कि आपके पास LongAndCamelCaseStdio.hनिम्नलिखित घोषणा के साथ सिस्टम हैडर है PrintToConsole

void PrintToConsole(int);

कुछ भी मददगार नहीं है, सब ठीक है। अब, आइए देखें कि इस फ़ंक्शन की वास्तविक परिभाषा की जांच करके विक्रेता कितना दुष्ट है (या शायद इतना बुरा नहीं है, अपरिभाषित व्यवहार अधिक बुरा हो सकता है)।

int printf(const char *, ...);
void exit(int);

void PrintToConsole(int num) {
    printf("%d\n", num);
    exit(0);
}

कंपाइलर को वास्तव में यह मानना ​​पड़ता है कि किसी भी मनमाने ढंग से फंक्शन कंपाइलर को यह पता नहीं होता है कि वह क्या करता है या किसी अपवाद से बाहर निकल सकता है (C ++ के मामले में)। आप नोटिस कर सकते हैं कि *((char*)NULL) = 0;निष्पादित नहीं किया जाएगा, क्योंकि PrintToConsoleकॉल के बाद निष्पादन जारी नहीं रहेगा ।

अपरिभाषित व्यवहार PrintToConsoleवास्तव में लौटने पर हमला करता है। संकलक को उम्मीद है कि ऐसा नहीं होगा (क्योंकि इससे कार्यक्रम अपरिभाषित व्यवहार को अंजाम देने में कोई फर्क नहीं पड़ेगा), इसलिए कुछ भी हो सकता है।

हालांकि, चलो कुछ और पर विचार करें। मान लें कि हम अशक्त जाँच कर रहे हैं, और अशक्त जाँच के बाद चर का उपयोग करें।

int putchar(int);

const char *warning;

void lol_null_check(const char *pointer) {
    if (!pointer) {
        warning = "pointer is null";
    }
    putchar(*pointer);
}

इस मामले में, यह नोटिस करना आसान है कि lol_null_checkएक गैर-पूर्ण सूचक की आवश्यकता है। वैश्विक गैर-वाष्पशील warningचर को असाइन करना कुछ ऐसा नहीं है जो कार्यक्रम से बाहर निकल सकता है या किसी अपवाद को फेंक सकता है। यह pointerभी गैर-वाष्पशील है, इसलिए यह फ़ंक्शन के बीच में जादुई रूप से इसके मूल्य को नहीं बदल सकता है (यदि ऐसा होता है, तो यह अपरिभाषित व्यवहार है)। कॉलिंग lol_null_check(NULL)अपरिभाषित व्यवहार का कारण बनेगी जिसके कारण चर को असाइन नहीं किया जा सकता (क्योंकि इस बिंदु पर, तथ्य यह है कि कार्यक्रम अपरिभाषित व्यवहार को निष्पादित करता है)।

हालांकि, अपरिभाषित व्यवहार का मतलब कार्यक्रम कुछ भी कर सकता है। इसलिए, कुछ भी अपरिभाषित व्यवहार को समय में वापस जाने से नहीं रोकता है, और निष्पादन की पहली पंक्ति से पहले आपके प्रोग्राम को क्रैश कर रहा है int main()। यह अपरिभाषित व्यवहार है, इसका कोई मतलब नहीं है। यह टाइपिंग 3 के बाद भी दुर्घटनाग्रस्त हो सकता है, लेकिन अपरिभाषित व्यवहार समय में वापस चला जाएगा, और इससे पहले कि आप भी टाइप करें 3. दुर्घटना और जो जानता है, शायद अपरिभाषित व्यवहार आपके सिस्टम रैम को अधिलेखित कर देगा, और आपके सिस्टम को 2 सप्ताह बाद क्रैश करने का कारण होगा, जबकि आपका अपरिभाषित कार्यक्रम नहीं चल रहा है।


सभी वैध बिंदु। PrintToConsoleप्रोग्राम-एक्सटर्नल साइड-इफ़ेक्ट डालने का मेरा प्रयास है जो क्रैश होने के बाद भी दिखाई देता है और दृढ़ता से अनुक्रमित होता है। मैं एक ऐसी स्थिति बनाना चाहता था जहाँ हम यह बता सकें कि क्या इस कथन को अनुकूलित किया गया था। लेकिन आप सही हैं कि यह कभी वापस नहीं आ सकता है ।; वैश्विक के लिए लेखन का आपका उदाहरण अन्य अनुकूलन के अधीन हो सकता है जो यूबी से संबंधित नहीं हैं। उदाहरण के लिए एक अप्रयुक्त वैश्विक को हटाया जा सकता है। क्या आपके पास बाहरी साइड-इफ़ेक्ट बनाने के लिए एक विचार है जो नियंत्रण वापस करने की गारंटी है?
usr

क्या कोई बाहरी-विश्व-व्यापी दुष्परिणाम कोड द्वारा उत्पन्न किया जा सकता है जो एक संकलक रिटर्न मानने के लिए स्वतंत्र होगा? मेरी समझ से, यहां तक ​​कि एक विधि जो केवल एक volatileचर पढ़ती है वह वैध रूप से I / O ऑपरेशन को चालू कर सकती है जो बदले में वर्तमान धागे को तुरंत बाधित कर सकती है; इससे पहले कि कुछ और प्रदर्शन करने का मौका हो, बाधा हैंडलर धागे को मार सकता है। मुझे ऐसा कोई औचित्य नहीं दिखता जिसके द्वारा कंपाइलर उस बिंदु से पहले अपरिभाषित व्यवहार को आगे बढ़ा सके।
सुपरकैट

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

1

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

तीनों कोड स्निपेट के बारे में आपका तर्क सही है। विशेष रूप से, एक कंपाइलर किसी भी ऐसे बयान का इलाज कर सकता है जो बिना शर्त व्यवहार को अपरिभाषित करता है जिस तरह से जीसीसी व्यवहार करता है __builtin_unreachable(): एक अनुकूलन संकेत के रूप में कि कथन अनुपलब्ध है (और इस तरह, कि सभी कोड पथ बिना शर्त के भी पहुंच से बाहर है)। अन्य समान अनुकूलन निश्चित रूप से संभव हैं।


1
जिज्ञासा से बाहर, जब __builtin_unreachable()समय के साथ पिछड़े और आगे बढ़ने वाले प्रभाव होने लगे? किसी चीज़ को देखते हुए extern volatile uint32_t RESET_TRIGGER; void RESET(void) { RESET_TRIGGER = 0xAA55; __memorybarrier(); __builtin_unreachable(); }मैं देख सकता हूं builtin_unreachable()कि संकलक को यह बताने के लिए अच्छा है कि यह returnनिर्देश को छोड़ सकता है , लेकिन यह कहने से अलग होगा कि पूर्ववर्ती कोड छोड़ा जा सकता है।
सुपरकाट

RESET_TRIGGER अस्थिर होने के कारण @supercat उस स्थान पर लिखने के लिए मनमाना दुष्प्रभाव हो सकता है। संकलक के लिए यह एक अपारदर्शी विधि कॉल की तरह है। इसलिए, यह साबित नहीं किया जा सकता है (और मामला नहीं है) जो __builtin_unreachableपहुंच गया है। यह कार्यक्रम परिभाषित है।
usr

@usr: मुझे लगता है कि निम्न-स्तरीय संकलक वाष्पशील पहुंच का इलाज अपारदर्शी विधि कॉल के रूप में करना चाहिए, लेकिन न तो क्लैंग और न ही जीसीसी ऐसा करता है। अन्य बातों के अलावा, एक अपारदर्शी विधि कॉल किसी भी वस्तु के सभी बाइट्स का कारण बन सकती है, जिसका पता बाहरी दुनिया को पता चला है, और जिसे न तो लाइव restrictपॉइंटर द्वारा एक्सेस किया जा सकता है, न ही एक का उपयोग करके लिखा जाएगा unsigned char*
सुपरकैट

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

@supercat मुझे लगता है कि आप सही हैं। ऐसा लगता है कि अस्थिर संचालन का "अमूर्त मशीन पर कोई प्रभाव नहीं" है और इसलिए यह कार्यक्रम को समाप्त नहीं कर सकता है या दुष्प्रभाव का कारण नहीं बन सकता है।
usr

1

कई प्रकार की चीजों के लिए कई मानक उन चीजों का वर्णन करने में बहुत प्रयास करते हैं जो कार्यान्वयन या SHOULD नहीं करते हैं, नामकरण का उपयोग IETF RFC 2119 में परिभाषित के समान है (हालांकि जरूरी नहीं कि उस दस्तावेज़ में परिभाषाओं का हवाला देते हुए)। कई मामलों में, उन चीजों का वर्णन जो कार्यान्वयन को उन मामलों को छोड़कर करना चाहिए, जहां वे बेकार या अव्यवहारिक होंगे, उन आवश्यकताओं से अधिक महत्वपूर्ण हैं जिनके अनुरूप सभी अनुरूप कार्यान्वयन को अनुरूप होना चाहिए।

दुर्भाग्य से, सी और सी ++ मानक उन चीजों के विवरणों को बढ़ाते हैं जो 100% आवश्यक नहीं हैं, फिर भी गुणवत्ता कार्यान्वयन से अपेक्षित होना चाहिए जो विपरीत व्यवहार का दस्तावेज नहीं करता है। एक सुझाव जो कार्यान्वयन कुछ करना चाहिए, यह देखने के रूप में देखा जा सकता है कि जो हीन नहीं हैं, और ऐसे मामलों में जहां यह आम तौर पर स्पष्ट होगा कि कौन सा व्यवहार उपयोगी होगा या व्यावहारिक, बनाम अव्यवहारिक और बेकार, किसी दिए गए कार्यान्वयन पर, वहाँ था ऐसे निर्णयों में हस्तक्षेप करने के लिए मानक के लिए बहुत कम कथित आवश्यकता।

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


यह एक अच्छा बिंदु है कि यूबी से बचने से उपयोगकर्ता कोड पर एक लागत लागू हो सकती है ।
usr

@usr: यह एक ऐसा बिंदु है जिसे आधुनिकतावादी पूरी तरह से याद करते हैं। क्या मुझे एक उदाहरण जोड़ना चाहिए? उदाहरण के लिए, यदि कोड का मूल्यांकन करने की आवश्यकता होती है x*y < zजब x*yअतिप्रवाह नहीं होता है, और ओवरफ्लो की स्थिति में या तो 0 या 1 मनमाना फैशन में होता है, लेकिन साइड-इफेक्ट के बिना, अधिकांश प्लेटफार्मों पर कोई कारण नहीं है कि दूसरी और तीसरी आवश्यकताओं को पूरा करना अधिक महंगा क्यों होना चाहिए पहले मिलना, लेकिन सभी मामलों में मानक-परिभाषित व्यवहार की गारंटी के लिए अभिव्यक्ति लिखने का कोई भी तरीका कुछ मामलों में महत्वपूर्ण लागत जोड़ देगा। (int64_t)x*y < zअभिकलन लागत को चौगुनी से अधिक कर सकता है के रूप में अभिव्यक्ति लिखना ...
सुपरकैट

... कुछ प्लेटफार्मों पर, और के रूप में यह लिख (int)((unsigned)x*y) < zएक संकलक रोजगार क्या अन्यथा उपयोगी बीजीय प्रतिस्थापन गया हो सकता है (उदाहरण के लिए से रोका जा सके अगर यह जानता है कि xऔर zबराबर और सकारात्मक हैं, यह करने के लिए मूल अभिव्यक्ति को आसान बनाने में कर सकता है y<0, लेकिन संस्करण अहस्ताक्षरित का उपयोग कर कंपाइलर को गुणा करने के लिए मजबूर करेगा)। यदि कंपाइलर गारंटी दे सकता है, भले ही मानक इसे अनिवार्य नहीं करता है, तो यह "उपज 0 या 1 को बिना किसी साइड-इफेक्ट के" बनाए रखेगा, उपयोगकर्ता कोड कंपाइलर अनुकूलन के अवसर दे सकता है जो इसे अन्यथा नहीं मिल सकता है।
सुपरकैट

हाँ, ऐसा लगता है कि अपरिभाषित व्यवहार के कुछ मामूली रूप यहाँ सहायक होंगे। प्रोग्रामर एक ऐसे मोड को चालू कर सकता है जो x*yअतिप्रवाह के मामले में सामान्य मूल्य का उत्सर्जन करने का कारण बनता है लेकिन किसी भी मूल्य पर। C / C ++ में विन्यास योग्य UB मेरे लिए महत्वपूर्ण है।
usr

@usr: यदि C89 मानक के लेखक यह कहते हुए सत्य कह रहे थे कि हस्ताक्षर किए गए छोटे अहस्ताक्षरित मूल्यों को बढ़ावा देना सबसे गंभीर रूप से टूटने वाला परिवर्तन था, और अज्ञानी मूर्ख नहीं थे, तो इसका मतलब यह होगा कि वे उन मामलों में उम्मीद करते थे, जहां प्लेटफ़ॉर्म उपयोगी व्यवहार संबंधी गारंटियों को परिभाषित करते हुए, ऐसे प्लेटफ़ॉर्म के लिए कार्यान्वयन उन गारंटियों को प्रोग्रामर के लिए उपलब्ध करवाते रहे हैं, और प्रोग्रामर उनका शोषण करते रहे हैं, ऐसे प्लेटफ़ॉर्म के लिए कंपाइलर इस तरह के व्यवहार की गारंटी देते रहेंगे कि स्टैंडर्ड ने उन्हें आदेश दिया था या नहीं
सुपरकैट
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.