Как исключения работают (негласно) в C++

Ошибка анализа: синтаксическая ошибка, неожиданная '['

Эта ошибка возникает в двух вариантах:

Вариант 1

$arr = [1, 2, 3];

Синтаксис инициализатора этого массива был введен только в PHP 5.4; это приведет к возникновению ошибки парсера в версиях до этого. Если возможно, обновите свою установку или используйте старый синтаксис:

$arr = array(1, 2, 3);

См. Также этот пример из руководства.

Вариант 2

$suffix = explode(',', 'foo,bar')[1];

Результаты функции разыменования массива также были введены в PHP 5.4. Если обновление невозможно, вам нужно использовать временную переменную:

$parts = explode(',', 'foo,bar');
$suffix = $parts[1];

См. Также этот пример из руководства.

108
задан Henrique Nascimento Gouveia 8 March 2018 в 15:11
поделиться

7 ответов

Вместо предположения я решил на самом деле посмотреть на сгенерированный код с маленькой частью кода 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:

Удивление! Нет никаких дополнительных инструкций вообще относительно нормального пути выполнения кода. Компилятор вместо этого генерировал дополнительные исключительные fixup блоки кода, на которые ссылаются через таблицу в конце функции (который на самом деле помещается на отдельный участок исполняемого файла). Вся работа сделана негласно стандартной библиотекой, на основе этих таблиц (_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

Еще больше таблиц обработки исключений и различной дополнительной информации.

Так, заключение, по крайней мере, для GCC на Linux: стоимость является дополнительным пространством (для обработчиков и таблиц), выдаются ли исключения плюс дополнительные расходы парсинга таблиц и выполнения обработчиков, когда исключение выдается. Если Вы используете исключения вместо кодов ошибок, и ошибка редка, это может быть быстрее, так как у Вас нет издержек тестирования на ошибки больше.

В случае, если Вы хотите больше информации, в особенности что весь __cxa_ функции делают, видят исходную спецификацию, из которой они произошли:

104
ответ дан CesarB 24 November 2019 в 03:33
поделиться

Исключения, являющиеся медленным , были верны в былые времена.
В самом современном компиляторе это больше не сохраняется.

Примечание: Просто, потому что у нас есть исключения, не означает, что мы не используем коды ошибки также. Когда ошибка может быть обработана локально коды ошибки использования. Когда ошибки требуют большего количества контекста для исключений использования исправления: Я записал его намного более красноречиво здесь: , Что принципы ведут Вашу политику обработки исключений?

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

, Когда исключение выдается, существует некоторая сделанная работа.
, Но необходимо сравнить это со стоимостью возврата кодов ошибки и проверки их полностью назад к указать, где ошибка может быть обработана. И более трудоемкий, чтобы записать и поддержать.

Также существует один глюк для новичков:
, Хотя Объекты исключения, как предполагается, являются небольшими, некоторые люди помещают много материала в них. Тогда у Вас есть стоимость копирования объекта исключения. Решение там два, свернитесь:

  • не помещают дополнительный материал в Ваше исключение.
  • Выгода ссылкой константы.

, По-моему, я держал бы пари, что тот же код за исключениями или более эффективен или по крайней мере так же сопоставим как код без исключений (но имеет весь дополнительный код для проверки функциональных ошибочных результатов). Помните, что Вы ничего не получаете бесплатно, что компилятор генерирует код, который необходимо было записать во-первых для проверки кодов ошибки (и обычно компилятор намного более эффективен, чем человек).

13
ответ дан Community 24 November 2019 в 03:33
поделиться

Существует много способов, которыми Вы могли реализовать исключения, но обычно они будут полагаться на некоторую базовую поддержку со стороны ОС. В Windows это - структурированный механизм обработки исключений.

существует достойное обсуждение деталей о Проекте Кода: , Как компилятор C++ реализует обработку исключений

, издержки исключений происходят, потому что компилятор должен генерировать код для отслеживания, которых объекты должны быть разрушены в каждом стековом фрейме (или более точно определить объем), если исключение распространяет из того объема. Если функция не имеет никаких локальных переменных на стеке, которые требуют, чтобы деструкторы были названы тогда, это не должно иметь потери производительности wrt обработка исключений.

Используя код возврата может только раскрутить единственный уровень стека за один раз, тогда как механизм обработки исключений может перейти, гораздо дальше отодвигают стек в одной операции, если нет ничего для него, чтобы сделать в промежуточных стековых фреймах.

12
ответ дан Rob Walker 24 November 2019 в 03:33
поделиться

Matt Pietrek написал превосходную статью о Win32 Структурированная Обработка исключений . В то время как эта статья была первоначально написана в 1997, она все еще применяется сегодня (но конечно только относится к Windows).

6
ответ дан Greg Hewgill 24 November 2019 в 03:33
поделиться

Эта статья исследует проблему и в основном находит, что на практике существует стоимость во время выполнения для исключений, хотя стоимость является довольно низкой, если исключение не выдается. Хорошая статья, рекомендуемая.

5
ответ дан Alastair 24 November 2019 в 03:33
поделиться

Друг меня записал немного, как Visual C++ обрабатывает исключения несколько лет назад.

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

2
ответ дан Nils Pipenbrinck 24 November 2019 в 03:33
поделиться

Все хорошие ответы.

кроме того, думайте о том, насколько легче это должно отладить код, который делает 'если проверки' как логические элементы наверху методов вместо того, чтобы позволить коду выдавать исключения.

Мой девиз - то, что легко записать код, который работает. Самая важная вещь состоит в том, чтобы записать код для следующего человека, который смотрит на него. В некоторых случаях это - Вы за 9 месяцев, и Вы не хотите проклинать свое имя!

0
ответ дан Kieveli 24 November 2019 в 03:33
поделиться
Другие вопросы по тегам:

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