3 回答

TA貢獻(xiàn)1817條經(jīng)驗(yàn) 獲得超6個(gè)贊
不用猜測(cè),我決定實(shí)際上是用一小段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);
}
我使用進(jìn)行了編譯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(),因此編譯器決定需要析構(gòu)函數(shù)的非內(nèi)聯(lián)副本。
.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:
驚喜!正常代碼路徑上根本沒有多余的指令。相反,編譯器生成了額外的離線修正代碼塊,這些代碼塊通過函數(shù)末尾的表引用(實(shí)際上放在可執(zhí)行文件的單獨(dú)部分中)。所有工作都是由標(biāo)準(zhǔn)庫(kù)在后臺(tái)基于這些表(_ZTI11MyExceptionis typeinfo for MyException)完成的。
好吧,這實(shí)際上對(duì)我來說并不令人驚訝,我已經(jīng)知道該編譯器是如何做到的。繼續(xù)匯編輸出:
.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
在這里,我們看到了引發(fā)異常的代碼。盡管沒有僅僅因?yàn)榭赡芤l(fā)異常而產(chǎn)生了額外的開銷,但是在實(shí)際引發(fā)和捕獲異常方面顯然存在很多開銷。其中大多數(shù)隱藏在中__cxa_throw,該必須:
在異常表的幫助下遍歷堆棧,直到找到該異常的處理程序?yàn)橹埂?/p>
展開堆棧,直到到達(dá)該處理程序?yàn)橹埂?/p>
實(shí)際調(diào)用處理程序。
將其與僅返回值的成本進(jìn)行比較,您會(huì)看到為什么僅將異常用于特殊收益的原因。
最后,匯編文件的其余部分:
.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數(shù)據(jù)。
.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
甚至更多的異常處理表,以及各種額外的信息。
因此,至少對(duì)于Linux上的GCC,得出的結(jié)論是:無論是否引發(fā)異常,開銷都是額外的空間(用于處理程序和表),加上在引發(fā)異常時(shí)解析表并執(zhí)行處理程序的額外開銷。如果使用異常而不是錯(cuò)誤代碼,并且錯(cuò)誤很少見,則錯(cuò)誤速度會(huì)更快,因?yàn)槟辉傩枰M(jìn)行錯(cuò)誤測(cè)試。

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

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