Pull to refresh

Comments 9

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

Проблема с прерываниями состоит в том, что:
1) Приложение не может включать/выключать их, это может делать только ядро. Это непереносимо.
2) Прерывание опять же приходит в ядро, после чего ядро должно каким-то образом сообщить об этом приложению. Например, в случае линукса, если происходит деление на ноль, приложению прилетает SIGFPE, который сложно хендлить (попробуй узнай какой поток его вызвал). И тоже непереносимо между разными ОС.

В статье незря упоминалось, что иногда переполнение — это абсолютно нормально. Например, при рассчете CRC. Если бы у нас на каждую итерацию CRC/SHA1/etc прилетало прерывние, мы бы получали по два переключения контекста, что уронило бы производительность практически в ноль.
И ещё небольшое дополнение: прерывание при целочисельном делени на ноль возникается кажется только в x86. Например, arm просто записывает 0 в качестве результата и всё. Стандарт C говорит что результат деления на 0 будет undefined, если не ошибаюсь.

Это кажется довольно логичным, потому что процессор не должен заниматься валидацией обрабатываеммых данных. А исключение при делении на 0 — это рудимент эпохи программируемых калькуляторов.
в случае линукса, если происходит деление на ноль, приложению прилетает SIGFPE, который сложно хендлить (попробуй узнай какой поток его вызвал)

SIGFPE всегда приходит в тот же самый поток, который совершил деление на 0.
Да, действительно. Тут я ошибся. SIGFPE это thread-directed сигнал.

Но всё равно, сигнал либо придет асинхронно и тогда попробуй обработай его правильно. Исключение то нельзя бросить из обработчика. Только хардкор, только longjmp что подходит только для C и то с большими оговорками.
А если обратывать сигнал синхронно, то проще уж проверять делитель или флаговый регистр, так хоть переключений контекста не будет.
Вот только из ЯВУ получить доступ к флаговому регистру проблематично. Все сильно завязано на платформу.
86 содержит инструкции по арифметике с учетом этого флага и условный переход.
Это позволяет сложить 2 длинных числа в один цикл без проверок и нормализации.
Компилятор может выполнить такую оптимизацию, а может и не выполнить.
Ну если мы пишем безопасный язык (например Rust), то мы можем заставить компилятор проверять флаговый регистр после каждой арифметической операции. Судя по приведенному ассемблерному коду, Rust так и делает.
А если мы пишем на чем-то типа С, то у нас есть целая куча либо быстрых, либо переносимых способов.

Так-то Rust пытается быть и безопасным и быстрым. (:

Ну так они предлагают разные средства. Хочешь быстро — используй wrapping_..., хочешь безопасно — используй checked_…

Кстати, новые gcc и clang поддерживают встроенные функции наподобие checked_. Правда, несовместимым между собой образом. Так что на C (в некоторых случаях) тоже можно складывать числа относительно быстро и безопасно.
Sign up to leave a comment.

Articles