@Mr-Governor
Губернирую

Как работают исключения?

Смотрел видео, читал статьи, но нигде толком не описано как работают исключения.

Может ли кто нибудь объяснить на низком уровне, пошагово, то как работают исключения в с++?

По подробней про то зачем там стек раскручивается и зачем.
  • Вопрос задан
  • 373 просмотра
Решения вопроса 1
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
Может ли кто нибудь объяснить на низком уровне, пошагово, то как работают исключения в с++?

Вот описание части Itanium ABI связанной с раскруткой стека:
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html

ABI процессоров других архитектур устроены в этом месте точно так же.

Вот подробное описание структур, используемых при раскрутке стека в коде генерируемом gcc:
www.airs.com/blog/archives/460
www.airs.com/blog/archives/464

Документы по ссылкам достаточно сложны для восприятия. Для облегчения понимания можно откомпилировать простой С++ код выбрасывающий и ловящий исключение и найти в нем части описанные в первом документе.
Например
void e_destructor();
void s_destructor();

struct E {
        int code;

        E(int c): code(c)
        {
        }
        ~E()
        {
                e_destructor();
        }
};

struct S {
        ~S()
        {
                s_destructor();
        }
};

void f(int v)
{
        throw E(v);
}

int g(void (*p)(int v), int v)
{
        try {
                struct S s;
                p(v);
        } catch(struct E e) {
                return e.code;
        } catch (int i) {
                return i;
        } catch (...) {
                throw;
        }
        return 0;
}

после g++ -O2 -S превращается в следующие фрагменты:
функция f:
_Z1fi:
.LFB9:
        .cfi_startproc
        pushq   %rbx
        .cfi_def_cfa_offset 16
        .cfi_offset 3, -16
        movl    %edi, %ebx
        movl    $4, %edi
        call    __cxa_allocate_exception
        movl    $_ZN1ED1Ev, %edx
        movl    %ebx, (%rax)  <---- инициализация E::code
        movl    $_ZTI1E, %esi
        movq    %rax, %rdi
        call    __cxa_throw
        .cfi_endproc

Здесь видны вызовы __cxa_allocate_exception, конструктора объекта класса E и __cxa_throw
функция g:
_Z1gPFviEi:
.LFB10:
        .cfi_startproc
        .cfi_personality 0x3,__gxx_personality_v0
        .cfi_lsda 0x3,.LLSDA10
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
        pushq   %rbx
        .cfi_def_cfa_offset 24
        .cfi_offset 3, -24
        movq    %rdi, %rax
        movl    %esi, %edi
        subq    $8, %rsp
        .cfi_def_cfa_offset 32
.LEHB0:
        call    *%rax  <--- вызов функции по указателю
.LEHE0:
.LEHB1:
        call    _Z12s_destructorv  <--- вызов деструктора объекта s при нормальном выходе из блока try
.LEHE1:
        xorl    %eax, %eax
.L17:
        addq    $8, %rsp
        .cfi_remember_state
        .cfi_def_cfa_offset 24
        popq    %rbx
        .cfi_def_cfa_offset 16
        popq    %rbp
        .cfi_def_cfa_offset 8
        ret

Хвост с обработчиками исключений:
.L13:
        .cfi_restore_state
        movq    %rdx, %rbx
        movq    %rax, %rbp
        call    _Z12s_destructorv
        movq    %rbx, %rdx
.L6:
        cmpq    $1, %rdx
        je      .L8
        cmpq    $2, %rdx
        jne     .L22
        movq    %rbp, %rdi
        call    __cxa_begin_catch
        movl    (%rax), %ebx
        call    __cxa_end_catch
        movl    %ebx, %eax
        jmp     .L17
.L14:
        movq    %rax, %rbp
        jmp     .L6
.L22:
        movq    %rbp, %rdi
        call    __cxa_begin_catch
.LEHB2:
        call    __cxa_rethrow
.LEHE2:
.L8:
        movq    %rbp, %rdi
        call    __cxa_get_exception_ptr
        movq    %rbp, %rdi
        movl    (%rax), %ebx
        call    __cxa_begin_catch
.LEHB3:
        call    _Z12e_destructorv
.LEHE3:
.LEHB4:
        call    __cxa_end_catch
.LEHE4:
        movl    %ebx, %eax
        jmp     .L17
.L16:
        movq    %rax, %rbx
        call    __cxa_end_catch
        movq    %rbx, %rdi
.LEHB5:
        call    _Unwind_Resume
.LEHE5:
.L15:
        movq    %rax, %rbx
        call    __cxa_end_catch
        movq    %rbx, %rdi
.LEHB6:
        call    _Unwind_Resume
.LEHE6:
        .cfi_endproc

Здесь видны вызовы __cxa_begin_catch и __cxa_end_catch, __cxa_rethrow повторно выбрасывающий пойманное исключение, __cxa_get_exception_ptr и _Unwind_Resume, вызываемый если блок catch не ловит это исключение.

Дальше идёт структура LSDA описанная в третьем документе.

Сама раскрутка стека в этом коде отсутствует. Она выполняется следующим кодом libgcc: фаза 1 и фаза 2.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы