C कोड में हैंडलिंग में त्रुटि


152

जब आप C लाइब्रेरी में सुसंगत तरीके से त्रुटियों से निपटने में त्रुटि की बात करते हैं, तो आप "सर्वोत्तम अभ्यास" पर क्या विचार करते हैं।

मेरे विचार से दो तरीके हैं:

हमेशा त्रुटि कोड वापस करें। एक विशिष्ट कार्य इस तरह दिखेगा:

MYAPI_ERROR getObjectSize(MYAPIHandle h, int* returnedSize);

हमेशा एक त्रुटि सूचक दृष्टिकोण प्रदान करें:

int getObjectSize(MYAPIHandle h, MYAPI_ERROR* returnedError);

पहले दृष्टिकोण का उपयोग करते समय कोड को इस तरह लिखना संभव है जहां त्रुटि से निपटने की जांच सीधे फ़ंक्शन कॉल पर रखी जाती है:

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

जो यहाँ पर एरर हैंडलिंग कोड से बेहतर लगता है।

MYAPIError error;
int size;
size = getObjectSize(h, &error);
if(error != MYAPI_SUCCESS) {
    // Error handling
}

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

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

संपादित करें: इस पर C ++ के विशिष्ट विचार भी तब तक सुनने में दिलचस्प होंगे जब तक वे अपवादों को शामिल नहीं कर रहे हैं क्योंकि यह मेरे लिए इस समय एक विकल्प नहीं है ...


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

जवाबों:


74

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

  • सभी संभावित त्रुटि-स्थिति को एक टाइप किए गए ईफम में संग्रहीत करें और इसे अपने परिवाद में उपयोग करें। सिर्फ रिटर्न या उससे भी बदतर मत करो, रिटर्न कोड के साथ ints या विभिन्न गणना मिश्रण।

  • एक फ़ंक्शन प्रदान करें जो त्रुटियों को कुछ मानव पठनीय में परिवर्तित करता है। सरल हो सकता है। बस एरर-एनम इन, कास्ट चार * आउट।

  • मुझे पता है कि यह विचार मल्टीथ्रेडेड उपयोग को थोड़ा मुश्किल बनाता है, लेकिन यह अच्छा होगा यदि एप्लिकेशन प्रोग्रामर एक वैश्विक त्रुटि-कॉलबैक सेट कर सकता है। इस तरह वे बग-हंट सत्रों के दौरान कॉलबैक में एक विराम बिंदु डाल सकेंगे।

आशा करता हूँ की ये काम करेगा।


5
आप क्यों कहते हैं, "यह विचार बहु-थ्रेडेड उपयोग को थोड़ा मुश्किल बना देता है।" मल्टी-थ्रेडिंग द्वारा किस भाग को कठिन बनाया जाता है? क्या आप एक त्वरित उदाहरण दे सकते हैं?
सईदहुसैन

1
@ क्रिप्टोकरंसी बस ने कहा: एक वैश्विक त्रुटि कॉलबैक को जो भी थ्रेड संदर्भ में लागू किया जा सकता है। यदि आप केवल त्रुटि का प्रिंट आउट लेते हैं तो आपको किसी भी समस्या का सामना नहीं करना पड़ेगा। यदि आप समस्याओं को ठीक करने का प्रयास करते हैं, तो आपको यह पता लगाना होगा कि किस कॉलिंग थ्रेड में त्रुटि हुई और इससे चीजें कठिन हो जाती हैं।
नेल्स पिपेनब्रिनक

9
क्या होगा यदि आप त्रुटि के अधिक विवरण को संवाद करना चाहते हैं? उदाहरण के लिए, आपके पास एक पार्सर त्रुटि है और वह सिंटैक्स त्रुटि का लाइन नंबर और कॉलम प्रदान करना चाहता है और इसे सभी को अच्छी तरह से प्रिंट करने का एक तरीका है।
पैन्ज़ी

1
@panzi तो आपको स्पष्ट रूप से एक संरचना को वापस करने की जरूरत है (या यदि संरचना वास्तव में बड़ी है तो आउट पॉइंटर का उपयोग करें) और एक स्ट्रिंग के रूप में संरचना को प्रारूपित करने के लिए एक फ़ंक्शन है।
विंगर सेंडोन

मैं यहां कोड में आपकी पहली 2 गोलियों को प्रदर्शित करता हूं: stackoverflow.com/questions/385975/error-handling-in-c-code/…
गेब्रियल स्टेपल्स

92

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

यदि केवल संभावित त्रुटियां प्रोग्रामर त्रुटियां हैं, तो एक त्रुटि कोड वापस न करें, फ़ंक्शन के अंदर जोर का उपयोग करें।

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


1
C में मुखर का एक उदाहरण मिला? (मैं सी से बहुत हरा हूं)
थोमथोम

यह आमतौर पर उतना ही सरल होता है, assert(X)जहां एक्स कोई भी वैध सी स्टेटमेंट होता है, जिसे आप सच होना चाहते हैं। देख stackoverflow.com/q/1571340/10396
एएसएचली

14
ऊ, पुस्तकालय कोड में कभी भी जोर का प्रयोग नहीं करते ! इसके अलावा, कोड के एक टुकड़े में त्रुटि से निपटने की विभिन्न शैलियों को मिक्स न करें जैसे कि दूसरों ने किया ...
mirabilos

10
मैं निश्चित रूप से शैलियों का मिश्रण नहीं करने के बारे में सहमत हूं। मैं आपके तर्क के बारे में उत्सुक हूं। मेरी समारोह प्रलेखन कहते हैं "तर्क एक्स नहीं होना चाहिए शून्य" या "Y इस enum का सदस्य होना आवश्यक है", की तुलना में क्या गलत है assert(X!=NULL);या assert(Y<enumtype_MAX);? इस उत्तर को प्रोग्रामर्स पर देखें और सवाल यह है कि यह अधिक विस्तार के लिए लिंक क्यों मुझे लगता है कि यह जाने का सही तरीका है।
एएसहेल्ली

8
@AShelly के साथ समस्या यह है कि वे आमतौर पर रिलीज़ बिल्ड में नहीं होते हैं।
कैलेमरियस

29

आम सी (और सी ++) त्रुटि हैंडलिंग तकनीकों में से प्रत्येक का उपयोग करने के लिए सिफारिशों के साथ सीएमयू के सीईआरटी से स्लाइड का एक अच्छा सेट है । सबसे अच्छी स्लाइड्स में से एक यह निर्णय वृक्ष है:

निर्णय ट्री को संभालने में त्रुटि

मैं व्यक्तिगत रूप से इस फ्लोकार्ट के बारे में दो चीजें बदलूंगा।

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

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

  1. C ++ अपवादों में बहुत विशेष शब्दार्थ हैं। यदि आप उन शब्दार्थों को नहीं चाहते हैं, तो C ++ अपवाद एक बुरा विकल्प है। एक अपवाद को फेंक दिए जाने के तुरंत बाद निपटा जाना चाहिए और डिजाइन उस मामले का पक्षधर है जहां एक त्रुटि को कॉलस्टैक को कुछ स्तरों पर खोलना होगा।

  2. C ++ फ़ंक्शन जो अपवादों को फेंकते हैं, बाद में अपवादों को फेंकने के लिए नहीं लपेटे जा सकते हैं, कम से कम वैसे भी अपवादों की पूरी लागत का भुगतान किए बिना नहीं। फ़ंक्शंस जो त्रुटि कोड लौटाते हैं, उन्हें C ++ अपवादों को फेंकने के लिए लपेटा जा सकता है, जिससे वे अधिक लचीले हो जाते हैं। C ++ का newयह अधिकार एक गैर-फेंक संस्करण प्रदान करके प्राप्त होता है।

  3. C ++ अपवाद अपेक्षाकृत महंगे हैं, लेकिन यह नकारात्मक अपवादों का समझदारी से उपयोग करने वाले कार्यक्रमों के लिए अधिक है। एक कार्यक्रम को केवल एक कोडपाथ पर अपवाद नहीं फेंकना चाहिए जहां प्रदर्शन एक चिंता का विषय है। यह वास्तव में कोई फर्क नहीं पड़ता कि आपका कार्यक्रम कितनी तेज़ी से त्रुटि और निकास की सूचना दे सकता है।

  4. कभी-कभी C ++ अपवाद उपलब्ध नहीं हैं। या तो वे शाब्दिक रूप से किसी के C ++ कार्यान्वयन में उपलब्ध नहीं हैं, या किसी के कोड दिशानिर्देश उन पर प्रतिबंध लगाते हैं।


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


5
आप ध्यान दें कि Google के C ++ कोडिंग मानक अभी भी कहते हैं
जोनाथन लेफ्लर

19

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

  • यदि फ़ंक्शन सरणी के रूप में अधिक जटिल आउटपुट देता है और यह लंबाई है तो आपको लौटने के लिए मनमाना संरचनाएं बनाने की आवश्यकता नहीं है।

    rc = func(..., int **return_array, size_t *array_length);
  • यह सरल, मानकीकृत त्रुटि से निपटने की अनुमति देता है।

    if ((rc = func(...)) != API_SUCCESS) {
       /* Error Handling */
    }
  • यह लाइब्रेरी फंक्शन में सिंपल एरर हैंडलिंग की अनुमति देता है।

    /* Check for valid arguments */
    if (NULL == return_array || NULL == array_length)
        return API_INVALID_ARGS;
  • टाइपडिफेड एनम के प्रयोग से एनम नाम डिबगर में दिखाई दे सकता है। यह हेडर फ़ाइल को लगातार परामर्श करने की आवश्यकता के बिना आसान डिबगिंग के लिए अनुमति देता है। इस एनम को एक स्ट्रिंग में अनुवाद करने के लिए एक फ़ंक्शन होने के साथ ही उपयोगी है।

उपयोग किए गए दृष्टिकोण की परवाह किए बिना सबसे महत्वपूर्ण मुद्दा सुसंगत होना है। यह फ़ंक्शन और तर्क नामकरण, तर्क आदेश और त्रुटि हैंडलिंग पर लागू होता है।


9

सेटजम्प का उपयोग करें ।

http://en.wikipedia.org/wiki/Setjmp.h

http://aszt.inf.elte.hu/~gsd/halado_cpp/ch02s03.html

http://www.di.unipi.it/~nids/docs/longjump_try_trow_catch.html

#include <setjmp.h>
#include <stdio.h>

jmp_buf x;

void f()
{
    longjmp(x,5); // throw 5;
}

int main()
{
    // output of this program is 5.

    int i = 0;

    if ( (i = setjmp(x)) == 0 )// try{
    {
        f();
    } // } --> end of try{
    else // catch(i){
    {
        switch( i )
        {
        case  1:
        case  2:
        default: fprintf( stdout, "error code = %d\n", i); break;
        }
    } // } --> end of catch(i){
    return 0;
}

#include <stdio.h>
#include <setjmp.h>

#define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
#define CATCH } else {
#define ETRY } }while(0)
#define THROW longjmp(ex_buf__, 1)

int
main(int argc, char** argv)
{
   TRY
   {
      printf("In Try Statement\n");
      THROW;
      printf("I do not appear\n");
   }
   CATCH
   {
      printf("Got Exception!\n");
   }
   ETRY;

   return 0;
}

2
कोड का दूसरा ब्लॉक फ्रांसेस्को निदितो के पेज पर कोड के पुराने संस्करण पर आधारित है , जो उत्तर के शीर्ष पर संदर्भित है। ETRYकोड संशोधित किया गया है के बाद से इस सवाल का जवाब लिखा गया था।
जोनाथन लेफ्लर

2
सेटजम्प एक भयानक त्रुटि से निपटने की रणनीति है। यह महंगा है, त्रुटि प्रवण (w / गैर-परिवर्तनशील स्थानीय लोगों ने अपने बदले हुए मूल्यों और सभी को बनाए नहीं रखा है) और संसाधनों को लीक करता है यदि आप सेटजम्प और लॉन्गजम्प कॉल के बीच में कोई भी आवंटित करते हैं। इससे पहले कि आप sigjmp / longjmp की लागत को पुनः प्राप्त करें, आपको 30 रिटर्न और इंट-वैल चेक की तरह करने में सक्षम होना चाहिए। ज्यादातर कॉलस्टैक्स उस गहराई तक नहीं जाते हैं, खासकर यदि आप पुनरावृत्ति पर भारी नहीं पड़ते हैं (और यदि आप करते हैं, तो आपको रिटर्न + चेक की लागत के अलावा पूर्ण समस्याएं हैं)।
PSkocik

1
यदि आप मेमोरी को मॉलोक करते हैं और फिर फेंक देते हैं, तो मेमोरी हमेशा के लिए लीक हो जाएगी। यह setjmpभी महंगा है, भले ही कोई त्रुटि कभी भी फेंक दी गई हो यह काफी सीपीयू समय और स्टैक स्थान का उपभोग करेगा। Windows के लिए gcc का उपयोग करते समय, आप C ++ के लिए अलग-अलग अपवाद हैंडलिंग विधियों के बीच चयन कर सकते हैं, उनमें से एक आधार पर है setjmpऔर यह आपके कोड को अभ्यास में 30% तक धीमा बनाता है।
Mecki

7

मैं व्यक्तिगत रूप से पूर्व दृष्टिकोण (एक त्रुटि सूचक लौटाता हूं) को प्राथमिकता देता हूं।

जहां आवश्यक परिणाम रिटर्न को इंगित करना चाहिए कि एक त्रुटि हुई, सटीक त्रुटि का पता लगाने के लिए एक अन्य फ़ंक्शन का उपयोग किया जा रहा है।

अपने getSize () उदाहरण में मैं विचार करूंगा कि आकार हमेशा शून्य या सकारात्मक होना चाहिए, इसलिए एक नकारात्मक परिणाम लौटना एक त्रुटि का संकेत दे सकता है, बहुत कुछ जैसे UNIX सिस्टम कॉल करते हैं।

मैं किसी भी पुस्तकालय के बारे में नहीं सोच सकता जो मैंने उपयोग किया है जो एक पॉइंटर के रूप में पारित त्रुटि ऑब्जेक्ट के साथ बाद के दृष्टिकोण के लिए जाता है। stdio, आदि सभी एक वापसी मूल्य के साथ जाते हैं।


1
रिकॉर्ड के लिए, एक पुस्तकालय जिसे मैंने बाद के दृष्टिकोण का उपयोग किया है, वह है माया प्रोग्रामिंग एपीआई। हालांकि यह C की बजाय c ++ लाइब्रेरी है। यह काफी असंगत है कि यह अपनी त्रुटियों को कैसे संभालता है और कभी-कभी त्रुटि को वापसी मूल्य के रूप में पारित किया जाता है और अन्य बार यह परिणाम को संदर्भ के रूप में पारित करता है।
लेजरालान

1
strtod मत भूलना, ठीक है, अंतिम तर्क केवल त्रुटियों को इंगित करने के लिए नहीं है, बल्कि यह भी करता है।
क्विनमार्स

7

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


7

C में एरर हैंडलिंग के लिए रिटर्निंग एरर कोड सामान्य तरीका है।

लेकिन हाल ही में हमने आउटगोइंग एरर पॉइंटर अप्रोच के साथ भी प्रयोग किया।

यह वापसी मूल्य दृष्टिकोण पर कुछ फायदे हैं:

  • आप अधिक सार्थक उद्देश्यों के लिए वापसी मूल्य का उपयोग कर सकते हैं।

  • उस त्रुटि पैरामीटर को लिखने से आपको त्रुटि को संभालने या उसे प्रचारित करने की याद आती है। (आप कभी नहीं, के वापसी मूल्य की जाँच भूल fcloseनहीं है?)

  • यदि आप एक त्रुटि सूचक का उपयोग करते हैं, तो आप इसे कॉल फ़ंक्शन के रूप में पास कर सकते हैं। यदि कोई फ़ंक्शन इसे सेट करता है, तो मान खो नहीं जाएगा।

  • त्रुटि चर पर एक डेटा ब्रेकपॉइंट सेट करके, आप यह पकड़ सकते हैं कि त्रुटि पहले कहाँ हुई थी। सशर्त ब्रेकपॉइंट सेट करके आप विशिष्ट त्रुटियों को भी पकड़ सकते हैं।

  • यह चेक को स्वचालित करना आसान बनाता है कि क्या आप सभी त्रुटियों को संभालते हैं। कोड कन्वेंशन आपको अपनी त्रुटि सूचक के रूप में कॉल करने के लिए बाध्य कर सकता है errऔर यह अंतिम तर्क होना चाहिए। तो स्क्रिप्ट स्ट्रिंग से मेल खा सकती है, err);फिर जांचें कि क्या यह उसके बाद है if (*err। वास्तव में व्यवहार में हमने एक मैक्रो CER(चेक इरेट रिटर्न) और CEG(चेक इरेटा गेटो) बनाया। इसलिए आपको इसे हमेशा टाइप करने की आवश्यकता नहीं है जब हम केवल त्रुटि पर लौटना चाहते हैं, और दृश्य अव्यवस्था को कम कर सकते हैं।

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


6

मैंने अतीत में बहुत सी प्रोग्रामिंग की है। और मैंने वास्तव में त्रुटि कोड रिटर्न वैल्यू की व्याख्या की। लेकिन क्या कई संभावित नुकसान हैं:

  • डुप्लिकेट त्रुटि संख्या, यह एक वैश्विक त्रुटियों के साथ हल किया जा सकता है। h फ़ाइल।
  • त्रुटि कोड की जांच करने के लिए भूल जाते हैं, यह एक सुराग और लंबे डिबगिंग घंटे के साथ हल किया जाना चाहिए। लेकिन अंत में आप सीखेंगे (या आपको पता चल जाएगा कि कोई और व्यक्ति डीबगिंग करेगा)।

2
दूसरी समस्या को उचित संकलक चेतावनी स्तर, उचित कोड समीक्षा तंत्र और स्थिर कोड विश्लेषक टूल द्वारा हल किया जा सकता है।
इल्या

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

6

UNIX दृष्टिकोण आपके दूसरे सुझाव के समान है। परिणाम या तो वापस लौटें या एकल "यह गलत हो गया" मान। उदाहरण के लिए, फ़ाइल सफलता पर फ़ाइल विवरणक को लौटा देगी या -1 विफलता पर। विफलता पर यह भी तय करता है errno, एक बाहरी वैश्विक पूर्णांक से संकेत मिलता है जो विफलता हुई।

इसके लायक क्या है, कोको भी इसी तरह का तरीका अपना रहा है। कई विधियाँ BOOL को लौटाती हैं, और एक NSError **पैरामीटर लेती हैं , ताकि असफल होने पर वे त्रुटि सेट करें और NO लौटें। तब त्रुटि से निपटने की तरह दिखता है:

NSError *error = nil;
if ([myThing doThingError: &error] == NO)
{
  // error handling
}

जो आपके दो विकल्पों के बीच कहीं है :-)।


5

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


मुझे बताने के लिए धन्यवाद। यह देखना दिलचस्प था।
लेजरलेन

5

यहाँ एक दृष्टिकोण है जो मुझे लगता है कि दिलचस्प है, जबकि कुछ अनुशासन की आवश्यकता है।

यह मानता है कि एक हैंडल-प्रकार चर वह उदाहरण है, जिस पर सभी एपीआई फ़ंक्शंस संचालित होते हैं।

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

उदाहरण:

MyHandle * h = MyApiCreateHandle();

/* first call checks for pointer nullity, since we cannot retrieve error code
   on a NULL pointer */
if (h == NULL)
     return 0; 

/* from here h is a valid handle */

/* get a pointer to the error struct that will be updated with each call */
MyApiError * err = MyApiGetError(h);


MyApiFileDescriptor * fd = MyApiOpenFile("/path/to/file.ext");

/* we want to know what can go wrong */
if (err->code != MyApi_ERROR_OK) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}

MyApiRecord record;

/* here the API could refuse to execute the operation if the previous one
   yielded an error, and eventually close the file descriptor itself if
   the error is not recoverable */
MyApiReadFileRecord(h, &record, sizeof(record));

/* we want to know what can go wrong, here using a macro checking for failure */
if (MyApi_FAILED(err)) {
    fprintf(stderr, "(%d) %s\n", err->code, err->message);
    MyApiDestroy(h);
    return 0;
}

4

पहला दृष्टिकोण बेहतर है IMHO:

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

4

मैं निश्चित रूप से पहला समाधान पसंद करता हूं:

int size;
if(getObjectSize(h, &size) != MYAPI_SUCCESS) {
  // Error handling
}

मैं इसे थोड़ा संशोधित करूंगा:

int size;
MYAPIError rc;

rc = getObjectSize(h, &size)
if ( rc != MYAPI_SUCCESS) {
  // Error handling
}

अतिरिक्त में मैं कभी भी त्रुटि के साथ वैध वापसी मूल्य को नहीं मिलाऊंगा, भले ही वर्तमान में फ़ंक्शन की गुंजाइश आपको ऐसा करने की अनुमति देती है, आपको कभी नहीं पता होगा कि भविष्य में किस तरह का कार्य कार्यान्वयन होगा।

और अगर हम पहले से ही एरर हैंडलिंग के बारे में बात कर रहे हैं, तो मैं goto Error;एरर हैंडलिंग कोड के रूप में सुझाव दूंगा, जब तक कि किसी undoफ़ंक्शन को एरर हैंडलिंग को सही तरीके से हैंडल करने के लिए नहीं कहा जा सकता है।


3

आप अपनी त्रुटि को वापस करने के बजाय क्या कर सकते हैं, और इस प्रकार आपको अपने फ़ंक्शन के साथ डेटा लौटने से मना कर रहे हैं, आपकी वापसी प्रकार के लिए एक आवरण का उपयोग कर रहा है :

typedef struct {
    enum {SUCCESS, ERROR} status;
    union {
        int errCode;
        MyType value;
    } ret;
} MyTypeWrapper;

फिर, बुलाया समारोह में:

MyTypeWrapper MYAPIFunction(MYAPIHandle h) {
    MyTypeWrapper wrapper;
    // [...]
    // If there is an error somewhere:
    wrapper.status = ERROR;
    wrapper.ret.errCode = MY_ERROR_CODE;

    // Everything went well:
    wrapper.status = SUCCESS;
    wrapper.ret.value = myProcessedData;
    return wrapper;
} 

कृपया ध्यान दें कि निम्नलिखित विधि के साथ, आवरण में MyType प्लस एक बाइट (अधिकांश संकलक) का आकार होगा, जो काफी लाभदायक है; और आपको अपने कार्य को कॉल करने पर ( returnedSizeया returnedErrorआपके द्वारा प्रस्तुत दोनों विधियों में) स्टैक पर एक और तर्क देने की जरूरत नहीं होगी


3

यहाँ निल्स पिपेनब्रिनक के उत्तर की पहली 2 गोलियों को प्रदर्शित करने के लिए एक सरल कार्यक्रम दिया गया है

उनकी पहली 2 गोलियां हैं:

  • सभी संभावित त्रुटि-स्थिति को एक टाइप किए गए ईफम में संग्रहीत करें और इसे अपने परिवाद में उपयोग करें। सिर्फ रिटर्न या उससे भी बदतर मत करो, रिटर्न कोड के साथ ints या विभिन्न गणना मिश्रण।

  • एक फ़ंक्शन प्रदान करें जो त्रुटियों को कुछ मानव पठनीय में परिवर्तित करता है। सरल हो सकता है। बस एरर-एनम इन, कास्ट चार * आउट।

मान लें कि आपने एक मॉड्यूल नाम लिखा है mymoduleसबसे पहले, mymodule.h में, आप अपने enum- आधारित त्रुटि कोड को परिभाषित करते हैं, और आप कुछ त्रुटि स्ट्रिंग लिखते हैं जो इन कोड के अनुरूप हैं। यहां मैं C स्ट्रिंग्स ( char *) का उपयोग कर रहा हूं , जो केवल तभी काम करता है जब आपके पहले एनम-आधारित त्रुटि कोड का मान 0 होता है, और आप इसके बाद की संख्याओं में हेरफेर नहीं करते हैं। यदि आप गैप या अन्य शुरुआती मूल्यों के साथ त्रुटि कोड संख्याओं का उपयोग करते हैं, तो आपको बस एक फ़ंक्शन का उपयोग करने के लिए मैप किए गए सी-स्ट्रिंग सरणी (जैसा कि मैं नीचे करता हूं) का उपयोग करने से बदलना होगा जो एक स्विच स्टेटमेंट का उपयोग करता है या यदि / यदि कथन है तो enum त्रुटि कोड से प्रिंट करने योग्य C स्ट्रिंग्स में मैप करने के लिए (जो मैं प्रदर्शित नहीं करता)। चुनना आपको है।

mymodule.h

/// @brief Error codes for library "mymodule"
typedef enum mymodule_error_e
{
    /// No error
    MYMODULE_ERROR_OK = 0,
    
    /// Invalid arguments (ex: NULL pointer where a valid pointer is required)
    MYMODULE_ERROR_INVARG,

    /// Out of memory (RAM)
    MYMODULE_ERROR_NOMEM,

    /// Make up your error codes as you see fit
    MYMODULE_ERROR_MYERROR, 

    // etc etc
    
    /// Total # of errors in this list (NOT AN ACTUAL ERROR CODE);
    /// NOTE: that for this to work, it assumes your first error code is value 0 and you let it naturally 
    /// increment from there, as is done above, without explicitly altering any error values above
    MYMODULE_ERROR_COUNT,
} mymodule_error_t;

// Array of strings to map enum error types to printable strings
// - see important NOTE above!
const char* const MYMODULE_ERROR_STRS[] = 
{
    "MYMODULE_ERROR_OK",
    "MYMODULE_ERROR_INVARG",
    "MYMODULE_ERROR_NOMEM",
    "MYMODULE_ERROR_MYERROR",
};

// To get a printable error string
const char* mymodule_error_str(mymodule_error_t err);

// Other functions in mymodule
mymodule_error_t mymodule_func1(void);
mymodule_error_t mymodule_func2(void);
mymodule_error_t mymodule_func3(void);

mymodule.c ने मेरे मैपिंग फंक्शन को मैप करने के लिए enum त्रुटि कोड से प्रिंट करने योग्य C स्ट्रिंग्स में शामिल किया है:

mymodule.c

#include <stdio.h>

/// @brief      Function to get a printable string from an enum error type
/// @param[in]  err     a valid error code for this module
/// @return     A printable C string corresponding to the error code input above, or NULL if an invalid error code
///             was passed in
const char* mymodule_error_str(mymodule_error_t err)
{
    const char* err_str = NULL;

    // Ensure error codes are within the valid array index range
    if (err >= MYMODULE_ERROR_COUNT)
    {
        goto done;
    }

    err_str = MYMODULE_ERROR_STRS[err];

done:
    return err_str;
}

// Let's just make some empty dummy functions to return some errors; fill these in as appropriate for your 
// library module

mymodule_error_t mymodule_func1(void)
{
    return MYMODULE_ERROR_OK;
}

mymodule_error_t mymodule_func2(void)
{
    return MYMODULE_ERROR_INVARG;
}

mymodule_error_t mymodule_func3(void)
{
    return MYMODULE_ERROR_MYERROR;
}

main.c में कुछ फ़ंक्शन को प्रदर्शित करने और उनसे कुछ त्रुटि कोड प्रिंट करने के लिए एक परीक्षण कार्यक्रम होता है:

main.c

#include <stdio.h>

int main()
{
    printf("Demonstration of enum-based error codes in C (or C++)\n");

    printf("err code from mymodule_func1() = %s\n", mymodule_error_str(mymodule_func1()));
    printf("err code from mymodule_func2() = %s\n", mymodule_error_str(mymodule_func2()));
    printf("err code from mymodule_func3() = %s\n", mymodule_error_str(mymodule_func3()));

    return 0;
}

आउटपुट:

सी (या सी ++) में
एनम -आधारित त्रुटि कोड का गलत प्रदर्शन mymodule_func1 () = MYMODULE_ERROR_OK
से गलत कोड mymodule_func2 () = MYMODULE_ERROR_INVARG
से गलत कोड mymodule_func3 (=_unun_3) से कोड का प्रदर्शन।

संदर्भ:

आप इस कोड को यहां खुद चला सकते हैं: https://onlinegdb.com/ByEbKLupS


2

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

मैं व्यक्तिगत रूप से शून्य के रूप में no_error के साथ नकारात्मक पूर्णांक के रूप में त्रुटि कोड लौटाता हूं , लेकिन यह आपको निम्नलिखित निम्नलिखित बग के साथ छोड़ देता है

if (MyFunc())
 DoSomething();

एक विकल्प में एक विफलता होती है जो हमेशा शून्य के रूप में वापस आ जाती है, और वास्तविक त्रुटि का विवरण प्रदान करने के लिए एक LastError () फ़ंक्शन का उपयोग करें।


2

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

किस तरह

किसी फ़ंक्शन से जानकारी वापस करने के 3 तरीके हैं:

  1. प्रतिलाभ की मात्रा
  2. तर्क
  3. बैंड में से, जिसमें गैर-स्थानीय गोटो (सेटजम्प / लॉन्गजम्प), फ़ाइल या वैश्विक स्कॉप्ड चर, फ़ाइल विकल्प आदि शामिल हैं।

प्रतिलाभ की मात्रा

आप केवल वापसी कर सकते हैं मूल्य एक ही वस्तु है, हालांकि, यह एक मनमाना परिसर हो सकता है। यहाँ एक त्रुटि वापसी समारोह का एक उदाहरण है:

  enum error hold_my_beer();

रिटर्न मानों का एक लाभ यह है कि यह कम घुसपैठ की त्रुटि से निपटने के लिए कॉल को चैन करने की अनुमति देता है:

  !hold_my_beer() &&
  !hold_my_cigarette() &&
  !hold_my_pants() ||
  abort();

यह न केवल पठनीयता के बारे में है, बल्कि एक समान तरीके से ऐसे फ़ंक्शन पॉइंटर्स की एक सरणी को संसाधित करने की अनुमति भी दे सकता है।

तर्क

आप तर्कों के माध्यम से एक से अधिक ऑब्जेक्ट के माध्यम से अधिक वापस आ सकते हैं, लेकिन सबसे अच्छा अभ्यास तर्क की कुल संख्या को कम रखने का सुझाव देता है (कहते हैं, <= 4):

void look_ma(enum error *e, char *what_broke);

enum error e;
look_ma(e);
if(e == FURNITURE) {
  reorder(what_broke);
} else if(e == SELF) {
  tell_doctor(what_broke);
}

बैंड से बाहर

सेटजम्प () के साथ आप एक जगह को परिभाषित करते हैं और आप एक अंतर मान को कैसे संभालना चाहते हैं, और आप लॉन्गजम्प () के माध्यम से उस स्थान पर नियंत्रण स्थानांतरित करते हैं। C में setjmp और longjmp का व्यावहारिक उपयोग देखें ।

क्या

  1. सूचक
  2. कोड
  3. वस्तु
  4. वापस कॉल करें

सूचक

एक त्रुटि संकेतक आपको केवल यह बताता है कि कोई समस्या है लेकिन उक्त समस्या की प्रकृति के बारे में कुछ भी नहीं है:

struct foo *f = foo_init();
if(!f) {
  /// handle the absence of foo
}

किसी फ़ंक्शन के लिए त्रुटि स्थिति को संवाद करने के लिए यह कम से कम शक्तिशाली तरीका है, हालांकि, परिपूर्ण यदि कॉलर किसी भी तरह से स्नातक की उपाधि में त्रुटि का जवाब नहीं दे सकता है।

कोड

एक त्रुटि कोड कॉलर को समस्या की प्रकृति के बारे में बताता है, और एक उपयुक्त प्रतिक्रिया (ऊपर से) के लिए अनुमति दे सकता है। यह रिटर्न मान हो सकता है, या त्रुटि तर्क के ऊपर लुक_मा () उदाहरण की तरह।

वस्तु

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

struct collection friends;
enum error *e = malloc(c.size * sizeof(enum error));
...
ask_for_favor(friends, reason);
for(int i = 0; i < c.size; i++) {
   if(reason[i] == NOT_FOUND) find(friends[i]);
}

त्रुटि सरणी को पूर्व-आबंटित करने के बजाय, आप इसे (पुनः) गतिशील रूप से आवंटित कर सकते हैं जैसा कि पाठ्यक्रम की आवश्यकता है।

वापस कॉल करें

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

 struct foo {
    ...
    void (error_handler)(char *);
 };

 void default_error_handler(char *message) { 
    assert(f);
    printf("%s", message);
 }

 void foo_set_error_handler(struct foo *f, void (*eh)(char *)) {
    assert(f);
    f->error_handler = eh;
 }

 struct foo *foo_init() {
    struct foo *f = malloc(sizeof(struct foo));
    foo_set_error_handler(f, default_error_handler);
    return f;
 }


 struct foo *f = foo_init();
 foo_something();

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

हालांकि, नियंत्रण का उलटा है। कॉलिंग कोड कॉल किया गया था, तो कॉलिंग कोड नहीं जानता है। जैसे, यह भी एक संकेतक का उपयोग करने के लिए समझ में आ सकता है।


1

संपादित करें: यदि आपको केवल अंतिम त्रुटि तक पहुंच की आवश्यकता है, और आप मल्टीथ्रेडेड वातावरण में काम नहीं करते हैं।

यदि आप C में काम करते हैं और बूल चर का समर्थन नहीं करते हैं, तो आप केवल सच / गलत (या किसी प्रकार का #define) वापस कर सकते हैं, और एक वैश्विक त्रुटि बफर है जो अंतिम त्रुटि पकड़ लेगा:

int getObjectSize(MYAPIHandle h, int* returnedSize);
MYAPI_ERROR LastError;
MYAPI_ERROR* getLastError() {return LastError;};
#define FUNC_SUCCESS 1
#define FUNC_FAIL 0

if(getObjectSize(h, &size) != FUNC_SUCCESS ) {
    MYAPI_ERROR* error = getLastError();
    // error handling
}

वास्तव में, लेकिन यह सी नहीं है यह ओएस द्वारा प्रदान किया जा सकता है या नहीं। यदि आप वास्तविक समय ऑपरेटिंग सिस्टम पर काम कर रहे हैं, उदाहरण के लिए आपके पास यह नहीं है।
इल्या

1

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


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


1
अच्छी तकनीक है। मैं यहां तक ​​कि gotoदोहराया के बजाय के साथ भी neater लगता है if। संदर्भ: एक , दो
Ant_222

0

अन्य महान उत्तरों के अलावा, मेरा सुझाव है कि आप प्रत्येक पंक्ति में एक पंक्ति को बचाने के लिए त्रुटि ध्वज और त्रुटि कोड को अलग करने का प्रयास करें, अर्थात:

if( !doit(a, b, c, &errcode) )
{   (* handle *)
    (* thine  *)
    (* error  *)
}

जब आपके पास बहुत सारी त्रुटि-जाँच होती है, तो यह थोड़ा सरलीकरण वास्तव में मदद करता है।

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