كيف الاستثناءات العمل (وراء الكواليس) في c++
-
08-07-2019 - |
سؤال
أنا أرى الناس يقولون أن الاستثناءات بطيئة, ولكن لم أرى أي دليل على ذلك.لذا بدلا من أن يسأل إذا كانت سوف تسأل كيف الاستثناءات العمل خلف الكواليس ، لذلك أنا يمكن أن تجعل القرارات عند استخدامها إذا كانت بطيئة.
ما أعرف الاستثناءات هي نفس الشيء كما فعل حفنة من العودة ولكن فإنه يتحقق أيضا عندما يحتاج إلى التوقف عن القيام عودة.كيف تحقق عند القيام التوقف ؟ أنا أخمن وأقول هناك الثانية المكدس الذي يحمل نوع من الاستثناء وكومة الموقع ثم لا يعود حتى يحصل هناك.أنا أيضا أظن أن المرة الوحيدة التي المكدس هو اللمس على رمي كل try/catch.AFAICT تنفيذ سلوك مماثل مع رمز الإرجاع أن تأخذ نفس المقدار من الوقت.لكن كل هذا تخمين ، لذلك أريد أن أعرف.
كيف الاستثناءات العمل حقا ؟
المحلول
بدلا من التخمين ، قررت أن ننظر في الواقع في التعليمات البرمجية التي تم إنشاؤها مع قطعة صغيرة من 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_
وظائف راجع مواصفات الأصلي جاءوا من:
نصائح أخرى
باستثناء بطيئة كان صحيح في الأيام الخوالي.
في أحدث مترجم هذا لم يعد صحيحا.
ملاحظة:فقط لأن لدينا استثناءات لا يعني أننا لا تستخدم رموز الخطأ أيضا.عندما خطأ يمكن التعامل معها محليا استخدام رموز الخطأ.عندما الأخطاء تتطلب المزيد من سياق تصحيح استخدام الاستثناءات:كتبت أكثر بلاغة هنا: ما هي المبادئ التوجيهية الخاصة بك معالجة الاستثناء السياسة ؟
تكلفة معالجة الاستثناء رمز عندما لا استثناءات تستخدم الصفر تقريبا.
عندما يتم طرح استثناء هناك بعض العمل المنجز.
ولكن عليك أن تقارن هذا مقابل تكلفة إرجاع رموز الخطأ و التحقق منها كل في طريق العودة إلى نقطة حيث الخطأ يمكن التعامل معها.أكثر تستغرق وقتا طويلا لكتابة والحفاظ عليها.
أيضا هناك واحد مسكتك للمبتدئين:
على الرغم من استثناء الكائنات هي من المفترض أن تكون صغيرة بعض الناس يضع الكثير من الاشياء بداخلها.ثم لديك تكاليف نسخ استثناء كائن.الحل هناك اثنين أضعاف:
- لا تضع الأشياء الإضافية في الاستثناء.
- الصيد const المرجعية.
في رأيي أراهن أن نفس الكود مع استثناءات إما أكثر كفاءة أو على الأقل مماثلة كما رمز دون استثناءات (ولكن لديه كل رمز إضافية للتحقق من وظيفة خطأ في النتائج).تذكر أنك لا تحصل على أي شيء مجانا المترجم هو توليد الكود يجب أن يكون مكتوب في المقام الأول إلى التحقق من رموز الخطأ (و عادة ما يكون المترجم هو أكثر كفاءة بكثير من الإنسان).
وهناك عدد من الطرق التي يمكن أن تنفذ استثناءات، ولكن عادة أنها سوف تعتمد على بعض الدعم الأساسي من نظام التشغيل. على ويندوز هذا هو منظم آلية معالجة الاستثناء.
وهناك مناقشة لائقة من التفاصيل حول مشروع القانون: كيف مترجم C ++ تنفذ معالجة الاستثناء
ويحدث فوق استثناءات لأن المترجم له لإنشاء رمز لتتبع الكائنات التي يجب دمر في كل إطار مكدس (أو نطاق بتعبير أدق) إذا يكاثر استثناء من هذا النطاق. إذا كان ليس لديه وظيفة المتغيرات المحلية على المكدس التي تتطلب تالفة ليتم استدعاؤها ثم لا ينبغي أن يكون لها التعامل جزاء أداء WRT استثناء.
وباستخدام رمز الإرجاع يمكن الاسترخاء فقط على مستوى واحد من المكدس في وقت واحد، في حين أن آلية التعامل مع استثناء لا يمكن القفز أبعد من ذلك بكثير التراجع المكدس في عملية واحدة إذا كان هناك أي شيء لذلك يجب القيام به في إطارات كومة المتوسطة.
وPietrek بيرني كتب مقالا ممتازة على Win32 والهيكلية معالجة استثناء . بينما هذه المادة مكتوبة أصلا في عام 1997، فإنه لا يزال ساريا اليوم (ولكن بالطبع لا ينطبق إلا على ويندوز).
هذه المقالة يدرس القضية وأساسا وجدت أنه في الممارسة العملية هناك انتخابات الاعادة تكلفة الوقت إلى الاستثناءات، على الرغم من أن تكلفة منخفضة نسبيا إذا لم يتم طرح استثناء. مادة جيدة، الموصى بها.
وهناك صديق لي وكتب بعض الشيء كيفية Visual C ++ يعالج الاستثناءات قبل بضع سنوات.
جميع إجابات جيدة.
وأيضا، والتفكير في كيف أنه أسهل بكثير إلى رمز التصحيح التي لا 'إذا الشيكات' كما البوابات في الجزء العلوي من الطرق بدلا من السماح رمز لرمي الاستثناءات.
وشعاري هو أنه من السهل كتابة التعليمات البرمجية التي يعمل. الشيء الأكثر أهمية هو أن كتابة التعليمات البرمجية للشخص القادم الذي يبدو في ذلك. في بعض الحالات، أنها تقوم في 9 أشهر، وكنت لا تريد أن تكون شتم اسمك!