Потому, что рантайм крестов предполагает уничтожение локальных объектов на стеке. Т.е. нельзя просто так взять и прыгнуть до catch, нужно вызывать деструкторы всех объектов, что были созданы на стеке по пути.
В некоторых других языках может происходить что-то подобное(например, если сборка мусора реализована как подсчет ссылок + детектор циклов).
И в целом другие языки с исключениями все равно медленнее крестов. Просто для других языков производительность не является столь важной, как для плюсов.
ящитаю это зачет, вместо того, чтобы поискать информацию о stack unwinding, надо прийти в Development и создать очередной долбоящерский тред. лор клоака
кривая раскрутка стека чаще всего главная проблема. освобождение ресурсов на куче, закрытие системных дескрипторов и прочее такое. всё это медленно и печально. и ещё и не всегда работает на сто процентов безопасно.
да и вообще разнообразие разных форматов реализации исключений, их несовместимость с сишным кодом, или иногда даже с системными библиотеками, частая несовместимость между кодом разных компиляторов заставляют трижды подумать, прежде чем их применять.
почему в других языках они — стандартная практика?
В любом императивном языке скорость броска исключения не играет никакой роли, на то оно и исключительная ситуация, что обрывает правильный путь выполнения операций. Тем более что время обработки исключения будет превышать на порядки время самого броска.
И не стоит путать количество проверок на ошибки и бросков исключений в коде с количеством реальных бросков в рантайме. Другое дело, что сами проверки типа if (error) throw могут съедать процессорное время. Для этого стоит пользоваться встроенными командами вроде GCC-шного __builtin_expect.
если во время раскрутки стека произойдёт исключение, которое упадёт в этот же поток, то в стеке окажется не совсем то что ожидается. в линаксе на это забили, а в фрибзде блокируют сигналы на время раскрутки. и ладно бы блокировали — делают это на каждый фрейм отдельно, т.е. чем дальше летит исключение тем медленнее.
Они считаются (и являются) более медленными, чем классическая обработка ошибок на основе кодов (либо возвращаемых из функции, либо глобальных вроде errno). Кроме этого они имеют ещё несколько недостатков: раздувают размер бинарника (независимо от того, используются исключения реально или нет), затрудняют отладку (особенно радует, когда какой-то «чудак» начинает на них городить логику), имеют сложный ABI (проброс исключения через несколько разделяемых библиотек, собранных разными компиляторами, может подарить много радостных часов в отладчике). В общем, есть плюсы и минусы.
Считается, что делает невозможным некоторые оптимизации. Кроме того, насколько я помню, пока исключение не выстрелило, ты за него ничего не платишь. Вот _постоянно_ обрабатывать ошибки исключениями - это медленно.
в С++ исключения шлются калбеком,проходя прослойку от ядра/иксов/glib/стандартной библиотеки/библиотек IO/...путь до твоего кода...и выполнение твоего кода на исключение-очевидно вобщем,чем длиннее путь тем дольше,исключения описанные в пределах одного бинарника(«аргумент» исключения не внешняя библиотека)-будет работать также как условие if/else (также медленно,если учитывать что if самая медленная операция для ЦП)
в JIT-прелоадинг и прекомпайлинг плюс шаблоны сценариев-ускоряют исключения до скорости case/switch в томже движке(что быстрее if/else в томже движке,но в разы медленее нативных компиляторов)
пока исключение не выстрелило, ты за него ничего не платишь
Ещё как платишь, насколько много — зависит от реализации исключений. В лучшем случае, размером бинарника. В каких-то реализациях компилятор может добавлять какие-то телодвижения на входе в каждую (!) функцию и/или блок try-catch
Иначе, на них потом альтернативно одарённые начинают стейт машины строить.
кстати да,это забавно выходит
познается вся кривость стандартной библиотеки си в винде-там половина событий(следовательно исключений) по таймерам проверяется,и довольно забавный рассинхрон потоков получается(причем даже в однопоточной программе) с забавными багами(абсолютно нелогичными и непонятными(влияющих даже на другие программы))
в линуксе тоже если исключение ждать от DE гнома/кде-которые скрипотовые...тоже самое что в винде
#! /usr/bin/env python3
def noexc(n):
a = []
for i in range(n):
if len(a) > 0:
a[0]
def exc(n):
a = []
for i in range(n):
try:
a[0]
except IndexError:
pass
if __name__ == '__main__':
from sys import argv
N = 128*1024*1024
if len(argv) > 1:
noexc(N)
else:
exc(N)
$ time ./slow_exceptions.py
real 1m13.330s
user 1m13.172s
sys 0m0.000s
$ time ./slow_exceptions.py no
real 0m18.698s
user 0m18.528s
sys 0m0.104s
В любом языке интерпретатор или компилятор может считать «исключительный» путь менее вероятным, может кроме случаев когда JIT слишком умный и придумает как оптимизировать исключения, когда они более частые. Но если теоретически предположить что мы напишем такой код на Java, где есть хотя бы малые шансы, на то что JIT такой умный, то все равно обычный цикл по сравнению с аллокацией обьектов в цикле сольет немеряно. Потому мне кажется ты тут меряешь скорость создания обьектов в том числе
забавно,на «высокоуровневых» исключениях,типа IO/памяти(где и логично использовать исключения а не х-рить в цикле) не замечал такой большой просадки,но да-твойже код на java
Начнем с того, что экспешен это, «ненормальное поведение» программы и кидаются они как раз в ситуациях, когда полимеры так и так просраны (в той или иной степени) так что пофиг на time deterministic.
Хотя в студенческую бытность у нас считалось нормой при написании лаб на додиезе, при обходе списка, определять конец списка по экспшену.
А теперь представь, едишь в машине, перед тобой внезапно появляется пешеход, ты давишь на тормоз, внезапно выясняется, что тормоз на одном из колес не может быть задействован в виду внезапно возникшей неисправности, и код по этомуповоду выбрасывает экзепшн, вместо того, чтобы, например, сделать простую проверку и goto и начать тормозить всем, что есть сразу же. В результате: задавленный пешеход.