แทนที่จะเดาฉันตัดสินใจที่จะดูโค้ดที่สร้างขึ้นด้วยโค้ด C ++ ชิ้นเล็ก ๆ และการติดตั้ง Linux ที่ค่อนข้างเก่า
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
ตารางการจัดการข้อยกเว้นและข้อมูลเพิ่มเติมที่หลากหลาย
ดังนั้นข้อสรุปอย่างน้อยสำหรับ GCC บน Linux: ค่าใช้จ่ายเป็นพื้นที่เพิ่มเติม (สำหรับตัวจัดการและตาราง) ไม่ว่าจะมีข้อยกเว้นหรือไม่บวกค่าใช้จ่ายเพิ่มเติมในการแยกวิเคราะห์ตารางและดำเนินการตัวจัดการเมื่อมีข้อยกเว้นเกิดขึ้น หากคุณใช้ข้อยกเว้นแทนรหัสข้อผิดพลาดและข้อผิดพลาดเกิดขึ้นน้อยมากข้อผิดพลาดนั้นอาจเร็วกว่าเนื่องจากคุณไม่มีค่าใช้จ่ายในการทดสอบข้อผิดพลาดอีกต่อไป
ในกรณีที่คุณต้องการข้อมูลเพิ่มเติมโดยเฉพาะอย่างยิ่งสิ่งที่__cxa_
ฟังก์ชันทั้งหมดทำดูข้อกำหนดดั้งเดิมที่มาจาก: