3 回答

TA貢獻1817條經驗 獲得超6個贊
不用猜測,我決定實際上是用一小段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:
驚喜!正常代碼路徑上根本沒有多余的指令。相反,編譯器生成了額外的離線修正代碼塊,這些代碼塊通過函數末尾的表引用(實際上放在可執行文件的單獨部分中)。所有工作都是由標準庫在后臺基于這些表(_ZTI11MyExceptionis 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
甚至更多的異常處理表,以及各種額外的信息。
因此,至少對于Linux上的GCC,得出的結論是:無論是否引發異常,開銷都是額外的空間(用于處理程序和表),加上在引發異常時解析表并執行處理程序的額外開銷。如果使用異常而不是錯誤代碼,并且錯誤很少見,則錯誤速度會更快,因為您不再需要進行錯誤測試。

TA貢獻1842條經驗 獲得超21個贊
例外是緩是在舊時代真的。
在大多數現代編譯器中,這不再成立。
注意:僅僅因為我們有例外并不意味著我們也不會使用錯誤代碼。如果可以在本地處理錯誤,請使用錯誤代碼。當錯誤需要更多上下文來更正時,請使用異常:我在這里雄辯地寫道:指導異常處理策略的原則是什么?
當不使用任何異常時,異常處理代碼的成本實際上為零。
引發異常時,將完成一些工作。
但是您必須將其與返回錯誤代碼并一路檢查它們以指出可以處理錯誤的位置的開銷進行比較。兩者都花費更多的時間來編寫和維護。
對于新手來說也有一個陷阱:
盡管Exception對象應該很小,但是有些人卻在其中放了很多東西。然后,您需要復制異常對象。解決方案有兩個方面:
不要把多余的東西放在例外中。
通過const引用捕獲。
在我看來,我敢打賭,帶有例外的同一代碼將比沒有例外的代碼更有效率,或者至少具有可比性(但具有檢查功能錯誤結果的所有額外代碼)。請記住,您沒有免費獲得任何東西,編譯器正在生成您應首先編寫的用于檢查錯誤代碼的代碼(通常,編譯器比人類更有效)。

TA貢獻1831條經驗 獲得超9個贊
有多種方法可以實現異常,但是通常它們將依賴于操作系統的某些基礎支持。在Windows上,這是結構化異常處理機制。
有關代碼項目的詳細信息,進行了不錯的討論:C ++編譯器如何實現異常處理
發生異常的開銷是因為,如果異常傳播到該范圍之外,則編譯器必須生成代碼來跟蹤必須在每個堆棧幀(或更確切地說是范圍)中銷毀哪些對象。如果函數在堆棧上沒有需要調用析構函數的局部變量,則它不應因異常處理而降低性能。
使用返回碼一次只能解開堆棧的單個級別,而如果在中間堆棧幀中無事可做,則異常處理機制可以在一次操作中進一步跳回堆棧。
添加回答
舉報