문제

나는 사람들이 예외가 느리다고 말하지만 아무런 증거도 보지 못한다고 말합니다. 따라서, 그들이 있는지 묻는 대신, 나는 예외가 현장 뒤에서 어떻게 작동하는지 묻습니다. 그래서 나는 그것들을 사용하는시기와 느리게 결정을 내릴 수 있습니다.

내가 아는 바에 따르면, 예외는 많은 수익을하는 것과 같은 일이지만 반품을 중단해야 할 때도 확인합니다. 언제 정지 해야하는지 어떻게 확인합니까? 나는 추측을하고 예외와 스택 위치의 유형을 보유하는 두 번째 스택이 있다고 말한 다음 거기에 도착할 때까지 돌아옵니다. 나는 또한 스택이 터치 인 유일한 시간은 던지고 모든 시도/캐치에 관한 것입니다. 반환 코드로 유사한 동작을 구현하는 AFAICT는 같은 시간이 걸립니다. 그러나 이것은 모두 추측이므로 알고 싶습니다.

예외는 실제로 어떻게 작동합니까?

도움이 되었습니까?

해결책

추측하는 대신 실제로 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

더 많은 예외 처리 테이블과 추가 정보가 있습니다.

따라서 Linux의 GCC의 경우 적어도 결론은 예외가 발생하는지 여부에 관계없이 추가 공간 (핸들러 및 테이블의 경우)과 예외가 발생하면 핸들러를 실행하는 추가 비용이 추가 공간입니다. 오류 코드 대신 예외를 사용하고 오류가 드물면 더 빠르게, 더 이상 오류 테스트의 오버 헤드가 없기 때문에.

더 많은 정보, 특히 모든 __cxa_ 함수는 다음과 같은 원래 사양을 참조하십시오.

다른 팁

예외가 느려집니다 ~였다 옛날에는 사실입니다.
대부분의 현대 컴파일러에서 이것은 더 이상 사실이 아닙니다.

참고 : 예외가 있다고해서 오류 코드도 사용하지 않는다는 의미는 아닙니다. 오류를 로컬로 처리 할 수있는 경우 오류 코드를 사용합니다. 오류가 수정을 위해 더 많은 컨텍스트가 필요할 때 예외를 사용합니다. 여기에 훨씬 더 설득력있게 썼습니다. 예외 처리 정책을 안내하는 원칙은 무엇입니까?

예외를 사용하지 않을 때 예외 처리 코드 비용은 실제로 0입니다.

예외가 발생하면 몇 가지 작업이 완료되었습니다.
그러나이를 반환 오류 코드 비용과 비교하고 오류를 처리 할 수있는 곳으로 돌아가는 비용과 비교해야합니다. 쓰기와 유지에 더 많은 시간이 소요됩니다.

또한 초보자에 대한 하나의 gotcha가 있습니다.
예외는 객체가 작아야하지만 일부 사람들은 그 안에 많은 물건을 넣습니다. 그런 다음 예외 객체를 복사하는 비용이 있습니다. 솔루션에는 두 배가 있습니다.

  • 예외에 여분의 물건을 넣지 마십시오.
  • Const 참조로 잡으십시오.

제 생각에는 예외가있는 동일한 코드가 예외없이 코드만큼 더 효율적이거나 비교할 수 있다고 생각합니다 (그러나 함수 오류 결과를 확인하기위한 추가 코드가 있습니다). 컴파일러가 오류 코드를 확인하기 위해 첫 번째로 작성해야 할 코드를 생성하고 있다는 것을 기억하십시오 (일반적으로 컴파일러는 사람보다 훨씬 효율적입니다).

예외를 구현할 수있는 여러 가지 방법이 있지만 일반적으로 OS의 기본 지원에 의존합니다. Windows에서 이것은 구조화 된 예외 처리 메커니즘입니다.

코드 프로젝트에 대한 세부 사항에 대한 괜찮은 논의가 있습니다. C ++ 컴파일러가 예외 처리를 구현하는 방법

예외가 해당 범위에서 전파되는 경우 컴파일러가 각 스택 프레임 (또는 더 정확하게 범위)에서 파괴되어야하는 객체를 추적하기 위해 코드를 생성해야하기 때문에 예외의 오버 헤드가 발생합니다. 함수가 스택에 로컬 변수가 없으면 소멸자를 호출 해야하는 경우 성능 페널티 WRT 예외 처리가 없어야합니다.

리턴 코드를 사용하면 한 번에 단일 레벨의 스택을 풀 수있는 반면, 예외 처리 메커니즘은 중간 스택 프레임에 할 일이 없으면 한 번의 작업에서 스택을 훨씬 더 뛰어 넘을 수 있습니다.

Matt Pietrek은 훌륭한 기사를 썼습니다 Win32 구조화 된 예외 처리. 이 기사는 원래 1997 년에 작성되었지만 오늘날에도 여전히 적용됩니다 (물론 Windows에만 적용됨).

이 기사 문제를 조사하고 기본적으로 예외가 발생하지 않으면 비용이 상당히 낮지 만 실제로 예외에 대한 런타임 비용이 있음을 발견합니다. 좋은 기사, 추천.

저의 친구는 몇 년 전에 시각적 C ++가 예외를 어떻게 처리하는지 약간 썼습니다.

http://www.xyzw.de/c160.html

모든 좋은 답변.

또한 코드가 예외를 던지지 않고 메서드 상단의 게이트로 'If Checks'가 수행하는 코드를 디버그하는 것이 얼마나 쉬운 지 생각해보십시오.

내 좌우명은 작동하는 코드를 작성하기가 쉽다는 것입니다. 가장 중요한 것은 다음을 보는 다음 사람을위한 코드를 작성하는 것입니다. 어떤 경우에는 9 개월 후에 당신이며 당신은 당신의 이름을 저주하고 싶지 않습니다!

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top