亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

為了賬號安全,請及時綁定郵箱和手機立即綁定
已解決430363個問題,去搜搜看,總會有你想問的

C ++中的異常如何工作(在幕后)

C ++中的異常如何工作(在幕后)

繁星coding 2019-12-09 09:47:03
我一直看到人們說例外情況很慢,但我從未見過任何證據。因此,我將詢問異常在后臺如何工作,而不是詢問它們是否存在,因此我可以決定何時使用它們以及它們是否緩慢。據我所知,異常與執行一堆返回是相同的事情,但是它還會檢查何時需要停止執行返回。如何檢查何時停止?我正在猜測,并說有第二個堆棧保存異常類型,然后堆棧位置返回直到到達那里。我還猜測唯一一次堆棧接觸是在擲球和每次嘗試/接球。AFAICT使用返回代碼實現類似行為將花費相同的時間。但這只是一個猜測,所以我想知道。異常如何真正起作用?
查看完整描述

3 回答

?
慕的地6264312

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,得出的結論是:無論是否引發異常,開銷都是額外的空間(用于處理程序和表),加上在引發異常時解析表并執行處理程序的額外開銷。如果使用異常而不是錯誤代碼,并且錯誤很少見,則錯誤速度會更快,因為您不再需要進行錯誤測試。


查看完整回答
反對 回復 2019-12-09
?
茅侃侃

TA貢獻1842條經驗 獲得超21個贊

例外是緩在舊時代真的。
在大多數現代編譯器中,這不再成立。

注意:僅僅因為我們有例外并不意味著我們也不會使用錯誤代碼。如果可以在本地處理錯誤,請使用錯誤代碼。當錯誤需要更多上下文來更正時,請使用異常:我在這里雄辯地寫道:指導異常處理策略的原則是什么?

當不使用任何異常時,異常處理代碼的成本實際上為零。

引發異常時,將完成一些工作。
但是您必須將其與返回錯誤代碼并一路檢查它們以指出可以處理錯誤的位置的開銷進行比較。兩者都花費更多的時間來編寫和維護。

對于新手來說也有一個陷阱:
盡管Exception對象應該很小,但是有些人卻在其中放了很多東西。然后,您需要復制異常對象。解決方案有兩個方面:

  • 不要把多余的東西放在例外中。

  • 通過const引用捕獲。

在我看來,我敢打賭,帶有例外的同一代碼將比沒有例外的代碼更有效率,或者至少具有可比性(但具有檢查功能錯誤結果的所有額外代碼)。請記住,您沒有免費獲得任何東西,編譯器正在生成您應首先編寫的用于檢查錯誤代碼的代碼(通常,編譯器比人類更有效)。


查看完整回答
反對 回復 2019-12-09
?
天涯盡頭無女友

TA貢獻1831條經驗 獲得超9個贊

有多種方法可以實現異常,但是通常它們將依賴于操作系統的某些基礎支持。在Windows上,這是結構化異常處理機制。

有關代碼項目的詳細信息,進行了不錯的討論:C ++編譯器如何實現異常處理

發生異常的開銷是因為,如果異常傳播到該范圍之外,則編譯器必須生成代碼來跟蹤必須在每個堆棧幀(或更確切地說是范圍)中銷毀哪些對象。如果函數在堆棧上沒有需要調用析構函數的局部變量,則它不應因異常處理而降低性能。

使用返回碼一次只能解開堆棧的單個級別,而如果在中間堆棧幀中無事可做,則異常處理機制可以在一次操作中進一步跳回堆棧。


查看完整回答
反對 回復 2019-12-09
  • 3 回答
  • 0 關注
  • 821 瀏覽
慕課專欄
更多

添加回答

舉報

0/150
提交
取消
微信客服

購課補貼
聯系客服咨詢優惠詳情

幫助反饋 APP下載

慕課網APP
您的移動學習伙伴

公眾號

掃描二維碼
關注慕課網微信公眾號