C ++ में अपवाद कैसे काम करते हैं (पर्दे के पीछे)


109

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

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

अपवाद वास्तव में कैसे काम करते हैं?



जवाबों:


105

अनुमान लगाने के बजाय, मैंने वास्तव में C ++ कोड के एक छोटे टुकड़े और कुछ पुराने लिनक्स इंस्टाल के साथ उत्पन्न कोड को देखने का फैसला किया।

class MyException
{
public:
    MyException() { }
    ~MyException() { }
};

void my_throwing_function(bool throwit)
{
    if (throwit)
        throw MyException();
}

void another_function();
void log(unsigned count);

void my_catching_function()
{
    log(0);
    try
    {
        log(1);
        another_function();
        log(2);
    }
    catch (const MyException& e)
    {
        log(3);
    }
    log(4);
}

मैंने इसे संकलित किया g++ -m32 -W -Wall -O3 -save-temps -c, और उत्पन्न विधानसभा फ़ाइल को देखा।

    .file   "foo.cpp"
    .section    .text._ZN11MyExceptionD1Ev,"axG",@progbits,_ZN11MyExceptionD1Ev,comdat
    .align 2
    .p2align 4,,15
    .weak   _ZN11MyExceptionD1Ev
    .type   _ZN11MyExceptionD1Ev, @function
_ZN11MyExceptionD1Ev:
.LFB7:
    pushl   %ebp
.LCFI0:
    movl    %esp, %ebp
.LCFI1:
    popl    %ebp
    ret
.LFE7:
    .size   _ZN11MyExceptionD1Ev, .-_ZN11MyExceptionD1Ev

_ZN11MyExceptionD1Evहै MyException::~MyException()तो संकलक फैसला किया कि यह नाशक की एक गैर इनलाइन प्रतिलिपि की जरूरत है,।

.globl __gxx_personality_v0
.globl _Unwind_Resume
    .text
    .align 2
    .p2align 4,,15
.globl _Z20my_catching_functionv
    .type   _Z20my_catching_functionv, @function
_Z20my_catching_functionv:
.LFB9:
    pushl   %ebp
.LCFI2:
    movl    %esp, %ebp
.LCFI3:
    pushl   %ebx
.LCFI4:
    subl    $20, %esp
.LCFI5:
    movl    $0, (%esp)
.LEHB0:
    call    _Z3logj
.LEHE0:
    movl    $1, (%esp)
.LEHB1:
    call    _Z3logj
    call    _Z16another_functionv
    movl    $2, (%esp)
    call    _Z3logj
.LEHE1:
.L5:
    movl    $4, (%esp)
.LEHB2:
    call    _Z3logj
    addl    $20, %esp
    popl    %ebx
    popl    %ebp
    ret
.L12:
    subl    $1, %edx
    movl    %eax, %ebx
    je  .L16
.L14:
    movl    %ebx, (%esp)
    call    _Unwind_Resume
.LEHE2:
.L16:
.L6:
    movl    %eax, (%esp)
    call    __cxa_begin_catch
    movl    $3, (%esp)
.LEHB3:
    call    _Z3logj
.LEHE3:
    call    __cxa_end_catch
    .p2align 4,,3
    jmp .L5
.L11:
.L8:
    movl    %eax, %ebx
    .p2align 4,,6
    call    __cxa_end_catch
    .p2align 4,,6
    jmp .L14
.LFE9:
    .size   _Z20my_catching_functionv, .-_Z20my_catching_functionv
    .section    .gcc_except_table,"a",@progbits
    .align 4
.LLSDA9:
    .byte   0xff
    .byte   0x0
    .uleb128 .LLSDATT9-.LLSDATTD9
.LLSDATTD9:
    .byte   0x1
    .uleb128 .LLSDACSE9-.LLSDACSB9
.LLSDACSB9:
    .uleb128 .LEHB0-.LFB9
    .uleb128 .LEHE0-.LEHB0
    .uleb128 0x0
    .uleb128 0x0
    .uleb128 .LEHB1-.LFB9
    .uleb128 .LEHE1-.LEHB1
    .uleb128 .L12-.LFB9
    .uleb128 0x1
    .uleb128 .LEHB2-.LFB9
    .uleb128 .LEHE2-.LEHB2
    .uleb128 0x0
    .uleb128 0x0
    .uleb128 .LEHB3-.LFB9
    .uleb128 .LEHE3-.LEHB3
    .uleb128 .L11-.LFB9
    .uleb128 0x0
.LLSDACSE9:
    .byte   0x1
    .byte   0x0
    .align 4
    .long   _ZTI11MyException
.LLSDATT9:

आश्चर्य! सामान्य कोड पथ पर कोई अतिरिक्त निर्देश नहीं हैं। संकलक के बजाय फ़ंक्शन के अंत में एक तालिका के माध्यम से संदर्भित अतिरिक्त आउट-ऑफ-लाइन फ़िक्अप कोड ब्लॉक उत्पन्न होता है, (जो वास्तव में निष्पादन योग्य के एक अलग अनुभाग पर रखा गया है)। सभी कार्य मानक पुस्तकालय द्वारा पर्दे के पीछे किया जाता है, इन तालिकाओं (के आधार पर _ZTI11MyExceptionहै typeinfo for MyException)।

ठीक है, यह वास्तव में मेरे लिए आश्चर्य की बात नहीं थी, मुझे पहले से ही पता था कि यह संकलक ने कैसे किया। विधानसभा उत्पादन के साथ जारी:

    .text
    .align 2
    .p2align 4,,15
.globl _Z20my_throwing_functionb
    .type   _Z20my_throwing_functionb, @function
_Z20my_throwing_functionb:
.LFB8:
    pushl   %ebp
.LCFI6:
    movl    %esp, %ebp
.LCFI7:
    subl    $24, %esp
.LCFI8:
    cmpb    $0, 8(%ebp)
    jne .L21
    leave
    ret
.L21:
    movl    $1, (%esp)
    call    __cxa_allocate_exception
    movl    $_ZN11MyExceptionD1Ev, 8(%esp)
    movl    $_ZTI11MyException, 4(%esp)
    movl    %eax, (%esp)
    call    __cxa_throw
.LFE8:
    .size   _Z20my_throwing_functionb, .-_Z20my_throwing_functionb

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

  • स्टैक को अपवाद तालिका की सहायता से तब तक चलाएं जब तक कि वह उस अपवाद के लिए हैंडलर न ढूंढ ले।
  • जब तक यह उस हैंडलर को नहीं मिलता तब तक स्टैक को अनविंड करें।
  • दरअसल हैंडलर को बुलाओ।

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

समाप्त करने के लिए, विधानसभा की बाकी फ़ाइल:

    .weak   _ZTI11MyException
    .section    .rodata._ZTI11MyException,"aG",@progbits,_ZTI11MyException,comdat
    .align 4
    .type   _ZTI11MyException, @object
    .size   _ZTI11MyException, 8
_ZTI11MyException:
    .long   _ZTVN10__cxxabiv117__class_type_infoE+8
    .long   _ZTS11MyException
    .weak   _ZTS11MyException
    .section    .rodata._ZTS11MyException,"aG",@progbits,_ZTS11MyException,comdat
    .type   _ZTS11MyException, @object
    .size   _ZTS11MyException, 14
_ZTS11MyException:
    .string "11MyException"

Typeinfo डेटा।

    .section    .eh_frame,"a",@progbits
.Lframe1:
    .long   .LECIE1-.LSCIE1
.LSCIE1:
    .long   0x0
    .byte   0x1
    .string "zPL"
    .uleb128 0x1
    .sleb128 -4
    .byte   0x8
    .uleb128 0x6
    .byte   0x0
    .long   __gxx_personality_v0
    .byte   0x0
    .byte   0xc
    .uleb128 0x4
    .uleb128 0x4
    .byte   0x88
    .uleb128 0x1
    .align 4
.LECIE1:
.LSFDE3:
    .long   .LEFDE3-.LASFDE3
.LASFDE3:
    .long   .LASFDE3-.Lframe1
    .long   .LFB9
    .long   .LFE9-.LFB9
    .uleb128 0x4
    .long   .LLSDA9
    .byte   0x4
    .long   .LCFI2-.LFB9
    .byte   0xe
    .uleb128 0x8
    .byte   0x85
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI3-.LCFI2
    .byte   0xd
    .uleb128 0x5
    .byte   0x4
    .long   .LCFI5-.LCFI3
    .byte   0x83
    .uleb128 0x3
    .align 4
.LEFDE3:
.LSFDE5:
    .long   .LEFDE5-.LASFDE5
.LASFDE5:
    .long   .LASFDE5-.Lframe1
    .long   .LFB8
    .long   .LFE8-.LFB8
    .uleb128 0x4
    .long   0x0
    .byte   0x4
    .long   .LCFI6-.LFB8
    .byte   0xe
    .uleb128 0x8
    .byte   0x85
    .uleb128 0x2
    .byte   0x4
    .long   .LCFI7-.LCFI6
    .byte   0xd
    .uleb128 0x5
    .align 4
.LEFDE5:
    .ident  "GCC: (GNU) 4.1.2 (Ubuntu 4.1.2-0ubuntu4)"
    .section    .note.GNU-stack,"",@progbits

इससे भी अधिक अपवाद तालिका को संभालने, और अतिरिक्त जानकारी मिश्रित।

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

यदि आप अधिक जानकारी चाहते हैं, तो विशेष रूप से सभी __cxa_कार्य क्या हैं, मूल विनिर्देश देखें जो वे यहाँ से आए थे:


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

5
त्रुटि लागत वास्तव में अधिक होने की संभावना है। अपवाद कोड डिस्क पर संभवतः अभी भी है! चूंकि त्रुटि हैंडलिंग कोड को सामान्य कोड से हटा दिया जाता है, इसलिए गैर-त्रुटि मामलों में कैश व्यवहार में सुधार होता है।
MSALERS

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

2
@ सुपरकैट: आप इस तरह से अपवाद हैंडलिंग कोड के साथ अपने आई-कैश को प्रदूषित कर रहे हैं। एक कारण है कि अपवाद हैंडलिंग कोड और टेबल सामान्य कोड से बहुत दूर होते हैं, आखिरकार।
सेसरब

1
@ सीजरबी: प्रत्येक कॉल के बाद एक निर्देश शब्द। बहुत अपमानजनक नहीं लगता है, विशेष रूप से केवल "बाहरी" कोड का उपयोग करके अपवाद हैंडलिंग के लिए दी गई तकनीकों को आमतौर पर उस कोड की आवश्यकता होती है जो हर समय एक वैध फ्रेम पॉइंटर बनाए रखता है (जो कुछ मामलों में 0 अतिरिक्त निर्देशों की आवश्यकता हो सकती है, लेकिन दूसरों में इससे अधिक की आवश्यकता हो सकती है) एक)।
सुपरकाट

13

अपवाद धीमी गति से किया जा रहा था पुराने दिनों में सच।
अधिकांश आधुनिक संकलक में अब यह सही नहीं है।

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

जब कोई अपवाद का उपयोग नहीं किया जा रहा है तो अपवाद हैंडलिंग कोड की लागत व्यावहारिक रूप से शून्य है।

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

इसके अलावा नौसिखियों के लिए एक गोचा है:
हालांकि अपवाद वस्तुओं को छोटा माना जाता है कुछ लोग उनके अंदर बहुत सारे सामान डालते हैं। फिर आपके पास अपवाद ऑब्जेक्ट को कॉपी करने की लागत है। समाधान दो गुना है:

  • अपने अपवाद में अतिरिक्त सामान न रखें।
  • नक्षत्र संदर्भ द्वारा पकड़ो।

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


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

@speedplane: मुझे लगता है। लेकिन कंपाइलर्स का पूरा बिंदु यह है कि हमें हार्डवेयर को समझने की आवश्यकता नहीं है (यह एक अमूर्त परत प्रदान करता है)। आधुनिक संकलक के साथ मुझे संदेह है कि क्या आप एक ऐसे व्यक्ति को खोज सकते हैं जो आधुनिक C ++ संकलक के हर पहलू को समझता है। तो क्यों समझ जटिल विशेषता एक्स को समझने से अलग है।
मार्टिन यॉर्क

आपको हमेशा यह जानने की जरूरत है कि हार्डवेयर क्या कर रहा है, यह डिग्री की बात है। C ++ (जावा या एक स्क्रिप्टेड भाषा पर) का उपयोग करने वाले कई लोग प्रदर्शन के लिए अक्सर ऐसा कर रहे हैं। उनके लिए, अमूर्त परत अपेक्षाकृत पारदर्शी होनी चाहिए, ताकि आपको यह पता चल सके कि धातु में क्या चल रहा है।
स्पीडप्लेन

@ स्पेसिडप्लेन: तब उन्हें सी का उपयोग करना चाहिए जहां अमूर्त परत डिजाइन द्वारा बहुत पतली है।
मार्टिन यॉर्क

12

ऐसे कई तरीके हैं जिनसे आप अपवादों को लागू कर सकते हैं, लेकिन आमतौर पर वे OS के कुछ अंतर्निहित समर्थन पर भरोसा करेंगे। विंडोज पर यह संरचित अपवाद हैंडलिंग तंत्र है।

कोड प्रोजेक्ट पर विवरण की सभ्य चर्चा है: कैसे एक सी ++ संकलक अपवाद हैंडलिंग को लागू करता है

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

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


"अपवादों का ओवरहेड इसलिए होता है क्योंकि कंपाइलर को कोड को जेनरेट करना पड़ता है कि किन वस्तुओं को प्रत्येक स्टैक फ्रेम (या अधिक सटीक दायरे) में नष्ट किया जाए" कंपाइलर को ऐसा नहीं करना है कि किसी भी तरह वापसी से वस्तुओं को नष्ट करने के लिए?

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

प्रफुल्लित करने वाला, मैं बस अपने आप को सोच रहा था "यह शांत नहीं होगा यदि प्रत्येक स्टैक फ्रेम इसमें वस्तुओं की संख्या, उनके प्रकार, नामों का ट्रैक रखता है, ताकि मेरा फ़ंक्शन स्टैक खोद सके और देख सके कि यह डीबगिंग के दौरान किन स्कोपों ​​को विरासत में मिला है" , और एक तरह से, यह ऐसा कुछ करता है, लेकिन मैन्युअल रूप से हमेशा एक तालिका को हर क्षेत्र के पहले चर के रूप में घोषित किया जाता है।
दिमित्री

6

मैट Pietrek ने Win32 संरचित अपवाद हैंडलिंग पर एक उत्कृष्ट लेख लिखा । जबकि यह लेख मूल रूप से 1997 में लिखा गया था, यह आज भी लागू होता है (लेकिन निश्चित रूप से केवल विंडोज पर लागू होता है)।


5

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



0

सभी अच्छे जवाब।

इसके अलावा, इस बारे में सोचें कि कोड को डिबग करना कितना आसान है जो कोड को अपवाद फेंकने की अनुमति देने के बजाय तरीकों के शीर्ष पर 'अगर चेक' करता है।

मेरा मकसद यह है कि काम करने वाले कोड लिखना आसान है। सबसे महत्वपूर्ण बात यह है कि इसे देखने वाले अगले व्यक्ति के लिए कोड लिखना है। कुछ मामलों में, यह 9 महीने में है, और आप अपने नाम को कोसना नहीं चाहते हैं!


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