C में त्रुटि प्रबंधन के लिए गोटो का वैध उपयोग?


92

यह सवाल वास्तव में थोड़ी देर पहले programming.reddit.com पर एक दिलचस्प चर्चा का परिणाम है । यह मूल रूप से निम्नलिखित कोड को उबालता है:

int foo(int bar)
{
    int return_value = 0;
    if (!do_something( bar )) {
        goto error_1;
    }
    if (!init_stuff( bar )) {
        goto error_2;
    }
    if (!prepare_stuff( bar )) {
        goto error_3;
    }
    return_value = do_the_thing( bar );
error_3:
    cleanup_3();
error_2:
    cleanup_2();
error_1:
    cleanup_1();
    return return_value;
}

gotoयहाँ का उपयोग जाने के लिए सबसे अच्छा तरीका प्रतीत होता है, जिसके परिणामस्वरूप सभी संभावनाओं का सबसे साफ और सबसे कुशल कोड है, या कम से कम इसलिए यह मुझे लगता है। कोड पूरा करने में स्टीव मैककोनेल का उद्धरण :

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

इस दृष्टिकोण के लिए एक और समर्थन लिनक्स डिवाइस ड्राइवर्स बुक से आता है , इस खंड में

तुम क्या सोचते हो? क्या यह मामला gotoC का वैध उपयोग है? क्या आप अन्य तरीकों को पसंद करेंगे, जो अधिक जटिल और / या कम कुशल कोड का उत्पादन करते हैं, लेकिन बचें goto?



@ एली: यदि आप टैग नहीं हटाते हैं और फ़ंक्शन (cleanup_3 ()?) को कोष्ठक में रखते हैं?

@Akito: तुम्हारा मतलब क्या है? क्या आप अपने सुझाव को पूर्ण कोड नमूने के साथ उत्तर के रूप में पोस्ट कर सकते हैं?
एली बेंडरस्की

@EliBendersky: कृपया मेरा उत्तर देखें।

विज़ुअल बेसिक (VBS और VB6 शामिल) से सबसे ज्यादा नफरत करने वाली चीजों में से एक था, जो on error goto errorत्रुटि से निपटने की प्रणाली थी :)
Manu343726

जवाबों:


61

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


1
क्या वह केवल उन टैगों को हटा नहीं सकता है और कार्यों को सीधे इसमें डाल सकता है? क्या यह अधिक पठनीय नहीं होगा?

8
@StartupCrazy, मुझे पता है कि यह वर्षों पुराना है, लेकिन इस साइट पर पदों की वैधता के लिए मैं यह इंगित करूंगा कि नहीं वह नहीं कर सकता। यदि उसे अपने कोड में गोटो एरर 3 में कोई त्रुटि मिलती है, तो वह 1 2 और 3 को क्लीन अप कर देगा, आपके सुसाइड सॉल्यूशन में वह केवल क्लीनअप चलाएगा 3. वह चीजों को घोंसला बना सकता है, लेकिन यह सिर्फ तीर का एंटीपैटर्न होगा, कुछ प्रोग्रामर इससे बच सकते हैं ।
gbtimmon

17

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

आप जिस चीज़ की रूपरेखा बनाते हैं वह त्रुटि से निपटने की समस्या के लिए एक सामान्य उपाय है - यह मेरे साथ तब तक ठीक है जब तक यह सावधानीपूर्वक उपयोग किया जाता है।

आपका विशेष उदाहरण निम्नानुसार सरल किया जा सकता है (चरण 1):

int foo(int bar)
{
    int return_value = 0;
    if (!do_something(bar)) {
        goto error_1;
    }
    if (!init_stuff(bar)) {
        goto error_2;
    }
    if (prepare_stuff(bar))
    {
        return_value = do_the_thing(bar);
        cleanup_3();
    }
error_2:
    cleanup_2();
error_1:
    cleanup_1();
    return return_value;
}

प्रक्रिया जारी रखना:

int foo(int bar)
{
    int return_value = 0;
    if (do_something(bar))
    {   
        if (init_stuff(bar))
        {
            if (prepare_stuff(bar))
            {
                return_value = do_the_thing(bar);
                cleanup_3();
            }
            cleanup_2();
        }
        cleanup_1();
    }
    return return_value;
}

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


23
हां, नेस्टेड समाधान व्यवहार्य विकल्पों में से एक है। दुर्भाग्य से यह कम व्यवहार्य हो जाता है क्योंकि घोंसले का स्तर गहरा हो जाता है।
एली बेंडरस्की

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

6
दुर्भाग्य से यह लूप्स के साथ काम नहीं करता है - यदि एक लूप के अंदर कोई त्रुटि होती है, तो एक गोटी बहुत होती है, झंडे स्थापित करने और जांचने के विकल्प की तुलना में बहुत अधिक क्लीनर और 'ब्रेक' स्टेटमेंट (जो कि चतुराई से प्रच्छन्न गोटो हैं)।
एडम रोसेनफील्ड

1
@ स्मिथ, बुलेट प्रूफ बनियान के बिना ड्राइविंग करना अधिक पसंद है।
स्ट्रेजर

6
मुझे पता है कि मैं यहाँ पर चर्चा कर रहा हूँ, लेकिन मुझे यह सलाह कमज़ोर लगती है - तीर विरोधी पैटर्न से बचना चाहिए।
KingRadical

16

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

मूल विचार यह है कि, प्रत्येक क्लीनअप कार्रवाई के लिए जिसे लेने की आवश्यकता हो सकती है, एक चर है, जिसके मूल्य से हम बता सकते हैं कि सफाई को करने की आवश्यकता है या नहीं।

मैं gotoपहले संस्करण दिखाऊंगा , क्योंकि यह मूल प्रश्न में कोड के करीब है।

int foo(int bar)
{
    int return_value = 0;
    int something_done = 0;
    int stuff_inited = 0;
    int stuff_prepared = 0;


    /*
     * Prepare
     */
    if (do_something(bar)) {
        something_done = 1;
    } else {
        goto cleanup;
    }

    if (init_stuff(bar)) {
        stuff_inited = 1;
    } else {
        goto cleanup;
    }

    if (prepare_stuff(bar)) {
        stufF_prepared = 1;
    } else {
        goto cleanup;
    }

    /*
     * Do the thing
     */
    return_value = do_the_thing(bar);

    /*
     * Clean up
     */
cleanup:
    if (stuff_prepared) {
        unprepare_stuff();
    }

    if (stuff_inited) {
        uninit_stuff();
    }

    if (something_done) {
        undo_something();
    }

    return return_value;
}

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

अब, कुछ का तर्क हो सकता है कि यह विधि कई अतिरिक्त चर जोड़ती है - और वास्तव में इस मामले में यह सच है - लेकिन व्यवहार में अक्सर एक मौजूदा चर पहले से ही ट्रैक करता है, या आवश्यक स्थिति को ट्रैक करने के लिए बनाया जा सकता है। उदाहरण के लिए, यदि prepare_stuff()वास्तव में कॉल है malloc(), या open()फिर रिटर्नेड पॉइंटर या फाइल डिस्क्रिप्टर रखने वाले वेरिएबल का उपयोग किया जा सकता है - उदाहरण के लिए:

int fd = -1;

....

fd = open(...);
if (fd == -1) {
    goto cleanup;
}

...

cleanup:

if (fd != -1) {
    close(fd);
}

अब, अगर हम अतिरिक्त रूप से त्रुटि स्थिति को एक चर के साथ ट्रैक करते हैं, तो हम gotoपूरी तरह से बच सकते हैं , और फिर भी सही ढंग से सफाई कर सकते हैं, बिना इंडेंटेशन के जो हमें और अधिक प्रारंभिकता को गहरा और गहरा करता है, जिसकी हमें आवश्यकता है:

int foo(int bar)
{
    int return_value = 0;
    int something_done = 0;
    int stuff_inited = 0;
    int stuff_prepared = 0;
    int oksofar = 1;


    /*
     * Prepare
     */
    if (oksofar) {  /* NB This "if" statement is optional (it always executes) but included for consistency */
        if (do_something(bar)) {
            something_done = 1;
        } else {
            oksofar = 0;
        }
    }

    if (oksofar) {
        if (init_stuff(bar)) {
            stuff_inited = 1;
        } else {
            oksofar = 0;
        }
    }

    if (oksofar) {
        if (prepare_stuff(bar)) {
            stuff_prepared = 1;
        } else {
            oksofar = 0;
        }
    }

    /*
     * Do the thing
     */
    if (oksofar) {
        return_value = do_the_thing(bar);
    }

    /*
     * Clean up
     */
    if (stuff_prepared) {
        unprepare_stuff();
    }

    if (stuff_inited) {
        uninit_stuff();
    }

    if (something_done) {
        undo_something();
    }

    return return_value;
}

फिर, इसकी संभावित आलोचनाएँ हैं:

  • क्या उन सभी को "यदि" प्रदर्शन में चोट नहीं लगी है? नहीं - क्योंकि सफलता के मामले में, आपको वैसे भी सभी जांच करनी होगी (अन्यथा आप सभी त्रुटि मामलों की जाँच नहीं कर रहे हैं); और विफलता के मामले में अधिकांश संकलक विफलता के अनुक्रम का अनुकूलन करेंगेif (oksofar) क्लीन जुकाम कोड (जीसीसी निश्चित रूप से करता है) के लिए एक एकल कूद चेक - और किसी भी मामले में, त्रुटि का मामला आमतौर पर प्रदर्शन के लिए कम महत्वपूर्ण होता है।
  • क्या यह अभी तक एक और चर नहीं जोड़ रहा है? इस मामले में हां, लेकिन अक्सर return_valueचर का उपयोग उस भूमिका को निभाने के लिए किया जा सकता oksofarहै जो यहां खेल रही है। यदि आप लगातार तरीके से त्रुटियों को वापस करने के लिए अपने कार्यों की संरचना करते हैं, तो आप ifप्रत्येक मामले में दूसरे से भी बच सकते हैं :

    int return_value = 0;
    
    if (!return_value) {
        return_value = do_something(bar);
    }
    
    if (!return_value) {
        return_value = init_stuff(bar);
    }
    
    if (!return_value) {
        return_value = prepare_stuff(bar);
    }

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

तो - यह (अभी तक) एक और शैली है जिसका उपयोग इस समस्या को हल करने के लिए किया जा सकता है। सही ढंग से उपयोग किया गया यह बहुत साफ, सुसंगत कोड के लिए अनुमति देता है - और किसी भी तकनीक की तरह, गलत हाथों में यह लंबे समय से घुमावदार और भ्रमित करने वाले कोड का उत्पादन कर सकता है :-)


2
ऐसा लगता है कि आपको पार्टी में देर हो गई है, लेकिन मुझे जवाब पसंद है!

लाइनस शायद आपके कोड को अस्वीकार कर देगा blogs.oracle.com/oswald/entry/is_goto_the_root_of
Fizz

1
@ user3588161: यदि वह चाहेगा, तो यह उसका विशेषाधिकार है - लेकिन मुझे यकीन नहीं है, आपके द्वारा जुड़े लेख के आधार पर, कि यह मामला है: ध्यान दें कि मैं जिस शैली का वर्णन कर रहा हूं, (1) सशर्त घोंसला न करें। मनमाने ढंग से गहराई से, और (2) कोई अतिरिक्त नहीं है "अगर" बयानों की तुलना में आपको वैसे भी क्या चाहिए (यह मानते हुए कि आप सभी रिटर्न कोड की जांच करने जा रहे हैं)।
भजन

भयानक गोटो और इससे भी बदतर तीर-एंटीपार्टन समाधान के बजाय इतना!
पैरामैग्नेटिक क्रोइसैंट

8

gotoकीवर्ड के साथ समस्या ज्यादातर गलत समझी जाती है। यह सादा-बुराई नहीं है। आपको बस उन अतिरिक्त नियंत्रण रास्तों के बारे में पता होना चाहिए जो आप हर गोटो के साथ बनाते हैं। आपके कोड और इसलिए इसकी वैधता के बारे में तर्क करना मुश्किल हो जाता है।

FWIW, यदि आप developer.apple.com ट्यूटोरियल देखते हैं, तो वे त्रुटि से निपटने के लिए गोटो दृष्टिकोण लेते हैं।

हम गोटो का इस्तेमाल नहीं करते हैं। एक उच्च महत्व रिटर्न वैल्यू पर रखा गया है। अपवाद हैंडलिंग के माध्यम से किया जाता है setjmp/longjmp- जो कुछ भी आप कर सकते हैं।


8
हालांकि मैंने कुछ मामलों में सेटजम्प / लॉन्गजम्प का उपयोग किया है, जहाँ इसके लिए कॉल किया गया था, मैं इसे गोटो (जो मैं भी उपयोग करता हूं, की तुलना में "कुछ हद तक कम आरक्षित रूप में, जब इसे कहा जाता है)" अधिक खराब "माना जाता है। जब भी मैं setjmp / longjmp का उपयोग करता हूं, तब या तो (1) लक्ष्य हर तरह से सब कुछ बंद कर देता है, जो उसकी वर्तमान स्थिति से प्रभावित नहीं होगा, या (2) लक्ष्य के भीतर नियंत्रित होने वाली हर चीज को पुन: व्यवस्थित करने जा रहा है setjmp / longjmp-guard ब्लॉक एक तरह से जो अपनी वर्तमान स्थिति से स्वतंत्र है।
सुपरकैट

4

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

यह सब है कि आप उपकरण का उपयोग कैसे करते हैं। आपके द्वारा प्रस्तुत किए गए (तुच्छ) मामले में, एक केस स्टेटमेंट एक ही तर्क को प्राप्त कर सकता है, यद्यपि अधिक उपरि के साथ। असली सवाल यह है, "मेरी गति की आवश्यकता क्या है?"

गोटो सिर्फ सादा उपवास है, खासकर यदि आप यह सुनिश्चित करने के लिए सावधान हैं कि यह एक छोटी छलांग के लिए संकलित है। उन अनुप्रयोगों के लिए बिल्कुल सही जहाँ गति एक प्रीमियम है। अन्य अनुप्रयोगों के लिए, यह संभवतया ओवरहैड को हिट करने के लिए समझ में आता है अगर / और + मामले को बनाए रखा जा सके।

याद रखें: गोटो अनुप्रयोगों को नहीं मारता है, डेवलपर्स अनुप्रयोगों को मारते हैं।

अद्यतन: यहाँ मामला उदाहरण है

int foo(int bar) { 
     int return_value = 0 ; 
     int failure_value = 0 ;

     if (!do_something(bar)) { 
          failure_value = 1; 
      } else if (!init_stuff(bar)) { 
          failure_value = 2; 
      } else if (prepare_stuff(bar)) { 
          return_value = do_the_thing(bar); 
          cleanup_3(); 
      } 

      switch (failure_value) { 
          case 2: cleanup_2(); 
          case 1: cleanup_1(); 
          default: break ; 
      } 
} 

1
क्या आप 'केस' विकल्प प्रस्तुत कर सकते हैं? इसके अलावा, मेरा तर्क है कि यह शून्य * से अलग है, जो कि सी में किसी भी गंभीर डेटा संरचना के अमूर्त के लिए आवश्यक है। मुझे नहीं लगता कि कोई भी गंभीर रूप से शून्य * का विरोध कर रहा है, और आपको इसके बिना एक भी बड़ा कोड आधार नहीं मिलेगा। यह।
एली बेंडरस्की

पुन: शून्य *, यह बिल्कुल मेरी बात है, नैतिक रूप से कुछ भी गलत नहीं है। स्विच / केस उदाहरण नीचे। int foo (इंट बार) {int return_value = 0; int failure_value = 0; if (do_something (बार)) {विफलता_वायु = 1; } और अगर ((init_stuff (बार)) {विफलता_वायु = 2; } और (if_stuff (बार)) {{return_value = do_the_thing (बार); cleanup_3 (); } स्विच (विफलता_वायु) {केस 2: cleanup_2 (); टूटना ; स्थिति 1: cleanup_1 (); टूटना ; डिफ़ॉल्ट: ब्रेक; }}
webmarc

5
@webmarc, क्षमा करें, लेकिन यह भयानक है! यो सिर्फ लेबल के साथ पूर्ण रूप से सिम्युलेटेड गोटो है - लेबल के लिए अपने स्वयं के गैर-वर्णनात्मक मूल्यों का आविष्कार करना और स्विच या मामले के साथ गोटो को लागू करना। Failure_value = 1 "goto cleanup_something" की तुलना में क्लीनर है?
एली बेंडरस्की

4
मुझे ऐसा लगता है कि आपने मुझे यहां सेट किया है ... आपका प्रश्न एक राय के लिए है, और मैं क्या पसंद करूंगा। फिर भी जब मैंने अपना जवाब पेश किया, तो यह भयानक है। :-( लेबल नामों के बारे में आपकी शिकायत के लिए, वे आपके उदाहरण के बाकी हिस्सों की तरह ही वर्णनात्मक हैं: cleanup_1, foo, bar। आप लेबल के नाम पर हमला क्यों कर रहे हैं, जब यह सवाल
जर्मेन के पास

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

2

GOTO उपयोगी है। यह ऐसा कुछ है जो आपका प्रोसेसर कर सकता है और यही कारण है कि आपको इसकी पहुंच होनी चाहिए।

कभी-कभी आप अपने फंक्शन में कुछ जोड़ना चाहते हैं और सिंगल गोटो आप ऐसा आसानी से कर सकते हैं। यह समय बचा सकता है ।।


3
आप अपने प्रोसेसर कर सकते हैं हर एक चीज के लिए उपयोग की आवश्यकता नहीं है। अधिकांश समय गोटो विकल्प की तुलना में अधिक भ्रामक है।
डेविड थॉर्नले 19

@DavidThornley: हाँ, आप कर , हर एक बात अपने प्रोसेसर कर सकते हैं की जरूरत पहुँच अन्यथा, आप अपने प्रोसेसर बर्बाद कर रहे हैं। गोटो प्रोग्रामिंग में सबसे अच्छा निर्देश है।
रॉन मैमन

1

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

  करना {
    .. सेट अप thing1 कि जल्दी बाहर निकलने के मामले में केवल सफाई की आवश्यकता होगी
    अगर (त्रुटि) ब्रेक;
    करना
    {
      .. thing2 सेट करें जिसे जल्दी निकलने की स्थिति में सफाई की आवश्यकता होगी
      अगर (त्रुटि) ब्रेक;
      // ***** देखने के लिए इस लाइन की जाँच करें
    } जबकि (0);
    .. सफाई चीज 2;
  } जबकि (0);
  .. सफाई की बात १;

लेकिन अगर फंक्शन होने पर क्लीनअप केवल होना चाहिए था, तो पहले टारगेट लेबल से ठीक पहले gotoलगाकर केस को संभाला जा सकता returnथा। उपरोक्त कोड returnके साथ चिह्नित लाइन पर एक जोड़ने की आवश्यकता होगी *****

"सामान्य स्थिति में भी सफाई", मैं अन्य चीजों के साथ / निर्माणों की gotoतुलना में अधिक स्पष्ट होने के उपयोग के संबंध में , क्योंकि लक्ष्य लेबल खुद व्यावहारिक रूप से "LOOK AT ME" दूर moreso की तुलना में रोते हैं।dowhile(0)breakdo / और while(0)निर्माणों की । "क्लीनअप केवल अगर एरर" केस है, तो returnस्टेटमेंट एक पठनीयता के दृष्टिकोण से सबसे खराब संभावित जगह के बारे में होना चाहिए (रिटर्न स्टेटमेंट आमतौर पर किसी फ़ंक्शन की शुरुआत में होना चाहिए, या फिर "जैसा दिखता है" समाप्त); एक होने returnसे ठीक पहले एक लक्ष्य लेबल अधिक आसानी कि योग्यता को पूरा करती है सिर्फ एक "लूप" के अंत से पहले एक की तुलना में।

BTW, एक परिदृश्य जहां मैं कभी goto- कभी त्रुटि से निपटने के लिए उपयोग करता हूं switch, एक बयान के भीतर होता है , जब कई मामलों के लिए कोड एक ही त्रुटि कोड साझा करता है। भले ही मेरा कंपाइलर अक्सर यह समझने के लिए पर्याप्त स्मार्ट होगा कि एक ही कोड के साथ कई मामले समाप्त हो जाएं, मुझे लगता है कि यह कहना स्पष्ट है:

 REPARSE_PACKET:
  स्विच (पैकेट [0])
  {
    मामला PKT_THIS_OPERATION:
      अगर (समस्या की स्थिति)
        गोटो PACKET_ERROR;
      ... इस THIS_OPERATION को संभालें
      टूटना;
    मामला PKT_THAT_OPERATION:
      अगर (समस्या की स्थिति)
        गोटो PACKET_ERROR;
      ... THAT_OPERATION हैंडल करें
      टूटना;
    ...
    मामला PKT_PROCESS_CONDITIONALLY
      अगर (packet_length <9)
        गोटो PACKET_ERROR;
      अगर (पैकेट से संबंधित पैकेट [4])
      {
        packet_length - = 5;
        memmove (पैकेट, पैकेट + 5, packet_length);
        गोटो REPARSE_PACKET;
      }
      अन्य
      {
        पैकेट [0] = PKT_CONDITION_SKIPPED;
        पैकेट [4] = packet_length;
        packet_length = 5;
        packet_status = READY_TO_SEND;
      }
      टूटना;
    ...
    चूक:
    {
     PACKET_ERROR:
      packet_error_count ++;
      packet_length = 4;
      पैकेट [0] = PKT_ERROR;
      packet_status = READY_TO_SEND;
      टूटना;
    }
  }   

यद्यपि एक gotoबयान के साथ बदल सकता है {handle_error(); break;}, और यद्यपि एक लिपटे सशर्त-निष्पादित पैकेट को संसाधित करने के साथ-साथ एक do/ while(0)लूप का उपयोग कर सकता है continue, मुझे नहीं लगता कि वास्तव में ए का उपयोग करने की तुलना में कोई भी स्पष्ट है goto। इसके अलावा, जबकि यह संभव है कि कोड का उपयोग PACKET_ERRORहर जगह goto PACKET_ERRORसे किया जाए, और जबकि एक कंपाइलर एक बार डुप्लिकेट किए गए कोड को लिख सकता है और अधिकांश घटनाओं को उस साझा प्रति में एक छलांग के साथ बदल सकता है, का उपयोग करके gotoस्थानों को नोटिस करना आसान बनाता है जो पैकेट को थोड़ा अलग तरीके से सेट करता है (जैसे अगर "सशर्त रूप से निष्पादित करें" निर्देश निष्पादित नहीं करने का निर्णय करता है)।


1

मैं व्यक्तिगत रूप से "द पॉवर ऑफ़ टेन - 10 रूल्स फॉर राइटिंग सेफ्टी क्रिटिकल कोड" का अनुयायी हूं ।

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


नियम: बहुत सरल नियंत्रण प्रवाह निर्माण के लिए सभी कोड को प्रतिबंधित करें - गोटो स्टेटमेंट, सेटजम्प या लॉन्गजम्प निर्माण का उपयोग न करें, और प्रत्यक्ष या अप्रत्यक्ष पुनरावृत्ति।

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


गोटो का उपयोग ख़त्म करना बुरा लगता है लेकिन:

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


22
इसके साथ समस्या यह है कि पूरी तरह से गायब होने का सामान्य तरीका यह है कि goto"चतुर" बूलियन्स के कुछ सेट का उपयोग गहन रूप से नेस्टेड या लूप में किया जाए। यह वास्तव में मदद नहीं करता है। हो सकता है कि आपके उपकरण बेहतर तरीके से चलें, लेकिन आप ऐसा नहीं करेंगे और आप अधिक महत्वपूर्ण हैं।
डोनल फैलो

1

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

int decode ( char * path_in , char * path_out )
{
  FILE * in , * out ;
  code c ;
  int len ;
  int res = 0  ;
  if ( path_in == NULL )
    in = stdin ;
  else
    {
      if ( ( in = fopen ( path_in , "r" ) ) == NULL )
        goto error_open_file_in ;
    }
  if ( path_out == NULL )
    out = stdout ;
  else
    {
      if ( ( out = fopen ( path_out , "w" ) ) == NULL )
        goto error_open_file_out ;
    }

  if( read_code ( in , & c , & longueur ) )
    goto error_code_construction ;

  if ( decode_h ( in , c , out , longueur ) )
  goto error_decode ;

  if ( 0 ) { error_decode: res = 1 ;}
  free_code ( c ) ;
  if ( 0 ) { error_code_construction: res = 1 ; }
  if ( out != stdout ) fclose ( stdout ) ;
  if ( 0 ) { error_open_file_out: res = 1 ; }
  if ( in != stdin ) fclose ( in ) ;
  if ( 0 ) { error_open_file_in: res = 1 ; }
  return res ;
 }

0

मुझे लगता है कि cleanup_3इसकी सफाई करनी चाहिए, फिर कॉल करें cleanup_2। इसी तरह, cleanup_2यह सफाई करना चाहिए, तो cleanup_1 पर कॉल करें। ऐसा प्रतीत होता है कि आप कभी भी करते हैं cleanup_[n], cleanup_[n-1]इसकी आवश्यकता होती है, इस प्रकार यह विधि की जिम्मेदारी होनी चाहिए (इसलिए, उदाहरण के लिए, cleanup_3कभी भी बिना कॉल किए नहीं जा सकता है cleanup_2और संभवतः रिसाव का कारण बन सकता है।)

उस दृष्टिकोण को देखते हुए, गोटो के बजाय, आप बस क्लीनअप रूटीन कहेंगे, फिर लौटेंगे।

gotoतरीका नहीं है गलत या बुरा है, हालांकि, यह सिर्फ ध्यान देने योग्य है कि यह जरूरी "साफ" दृष्टिकोण (IMHO) नहीं है।

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


4
यह कटौती नहीं करेगा - क्लीनअप संसाधनों के लिए विशिष्ट हैं, जो केवल इस दिनचर्या में इस क्रम में आवंटित किए जाते हैं। अन्य स्थानों पर, वे संबंधित नहीं हैं, इसलिए एक दूसरे से कॉल करने का कोई मतलब नहीं है।
एली बेंडरस्की

0

मुझे लगता है कि यहां दिए गए कोड के संबंध में सवाल बहुत ही भयानक है।

विचार करें:

  1. do_something (), init_stuff () और ready_stuff () यह जानने के लिए प्रकट होते हैं कि क्या वे असफल हो गए हैं, क्योंकि वे उस मामले में झूठे या शून्य वापस आ जाते हैं।
  2. राज्य की स्थापना के लिए जिम्मेदारी उन कार्यों की जिम्मेदारी प्रतीत होती है, क्योंकि कोई राज्य सीधे फू () में स्थापित नहीं किया जाता है।

इसलिए: do_something (), init_stuff () और ready_stuff () अपने स्वयं के क्लीनअप करने चाहिए । एक अलग cleanup_1 () फ़ंक्शन होने से जो do_something () के बाद सफाई करता है, एन्कैप्सुलेशन के दर्शन को तोड़ता है। यह खराब डिजाइन है।

यदि वे अपना स्वयं का सफाई करते हैं, तो फू () काफी सरल हो जाता है।

दूसरी ओर। यदि वास्तव में फू () ने अपना राज्य बनाया है जिसे फाड़ने की आवश्यकता है, तो गोटो उपयुक्त होगा।


0

यहाँ मैंने क्या पसंद किया है:

bool do_something(void **ptr1, void **ptr2)
{
    if (!ptr1 || !ptr2) {
        err("Missing arguments");
        return false;
    }
    bool ret = false;

    //Pointers must be initialized as NULL
    void *some_pointer = NULL, *another_pointer = NULL;

    if (allocate_some_stuff(&some_pointer) != STUFF_OK) {
        err("allocate_some_stuff step1 failed, abort");
        goto out;
    }
    if (allocate_some_stuff(&another_pointer) != STUFF_OK) {
        err("allocate_some_stuff step 2 failed, abort");
        goto out;
    }

    void *some_temporary_malloc = malloc(1000);

    //Do something with the data here
    info("do_something OK");

    ret = true;

    // Assign outputs only on success so we don't end up with
    // dangling pointers
    *ptr1 = some_pointer;
    *ptr2 = another_pointer;
out:
    if (!ret) {
        //We are returning an error, clean up everything
        //deallocate_some_stuff is a NO-OP if pointer is NULL
        deallocate_some_stuff(some_pointer);
        deallocate_some_stuff(another_pointer);
    }
    //this needs to be freed every time
    free(some_temporary_malloc);
    return ret;
}

0

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

static inline int foo_2(int bar)
{
    int return_value = 0;
    if ( prepare_stuff( bar ) ) {
        return_value = do_the_thing( bar );
    }
    cleanup_3();
    return return_value;
}

static inline int foo_1(int bar)
{
    int return_value = 0;
    if ( init_stuff( bar ) ) {
        return_value = foo_2(bar);
    }
    cleanup_2();
    return return_value;
}

int foo(int bar)
{
    int return_value = 0;
    if (do_something(bar)) {
        return_value = foo_1(bar);
    }
    cleanup_1();
    return return_value;
}

अंतरिक्ष के संदर्भ में, हम स्टैक में तीन बार वैरिएबल बना रहे हैं, जो अच्छा नहीं है, लेकिन -O2 को स्टैक से वेरिएबल को हटाने और इस सरल उदाहरण में एक रजिस्टर का उपयोग करने के साथ संकलन करने पर यह गायब हो जाता है। gcc -S -O2 test.cनीचे दिए गए उपरोक्त ब्लॉक से मुझे क्या मिला :

    .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 13
    .globl  _foo                    ## -- Begin function foo
    .p2align    4, 0x90
_foo:                                   ## @foo
    .cfi_startproc
## %bb.0:
    pushq   %rbp
    .cfi_def_cfa_offset 16
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
    .cfi_def_cfa_register %rbp
    pushq   %r14
    pushq   %rbx
    .cfi_offset %rbx, -32
    .cfi_offset %r14, -24
    movl    %edi, %ebx
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    callq   _do_something
    testl   %eax, %eax
    je  LBB0_6
## %bb.1:
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _init_stuff
    testl   %eax, %eax
    je  LBB0_5
## %bb.2:
    xorl    %r14d, %r14d
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _prepare_stuff
    testl   %eax, %eax
    je  LBB0_4
## %bb.3:
    xorl    %eax, %eax
    movl    %ebx, %edi
    callq   _do_the_thing
    movl    %eax, %r14d
LBB0_4:
    xorl    %eax, %eax
    callq   _cleanup_3
LBB0_5:
    xorl    %eax, %eax
    callq   _cleanup_2
LBB0_6:
    xorl    %eax, %eax
    callq   _cleanup_1
    movl    %r14d, %eax
    popq    %rbx
    popq    %r14
    popq    %rbp
    retq
    .cfi_endproc
                                        ## -- End function

.subsections_via_symbols

-1

मैं निम्नलिखित उदाहरण में वर्णित तकनीक का उपयोग करना पसंद करता हूं ...

struct lnode *insert(char *data, int len, struct lnode *list) {
    struct lnode *p, *q;
    uint8_t good;
    struct {
            uint8_t alloc_node : 1;
            uint8_t alloc_str : 1;
    } cleanup = { 0, 0 };

    // allocate node.
    p = (struct lnode *)malloc(sizeof(struct lnode));
    good = cleanup.alloc_node = (p != NULL);

    // good? then allocate str
    if (good) {
            p->str = (char *)malloc(sizeof(char)*len);
            good = cleanup.alloc_str = (p->str != NULL);
    }

    // good? copy data
    if(good) {
            memcpy ( p->str, data, len );
    }

    // still good? insert in list
    if(good) {
            if(NULL == list) {
                    p->next = NULL;
                    list = p;
            } else {
                    q = list;
                    while(q->next != NULL && good) {
                            // duplicate found--not good
                            good = (strcmp(q->str,p->str) != 0);
                            q = q->next;
                    }
                    if (good) {
                            p->next = q->next;
                            q->next = p;
                    }
            }
    }

    // not-good? cleanup.
    if(!good) {
            if(cleanup.alloc_str)   free(p->str);
            if(cleanup.alloc_node)  free(p);
    }

    // good? return list or else return NULL
    return (good? list: NULL);

}

स्रोत: http://blog.staila.com/?p=114


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

-1

हम Daynix CStepsलाइब्रेरी को "इन्टो फ़ंक्शंस" में " गेटो प्रॉब्लम " के लिए एक अन्य समाधान के रूप में उपयोग करते हैं। यहाँ और यहाँ
देखें ।


3
मैक्रो का दुरुपयोग (CSteps के साथ) अब तक के न्यायसंगत उपयोग से भी बदतर हैgoto
KingRadical
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.