С++: Поймать деление на нулевую ошибку

Вот простой фрагмент кода, где происходит деление на ноль. Я пытаюсь поймать его:

#include <iostream>

int main(int argc, char *argv[]) {
    int Dividend = 10;
    int Divisor = 0;

    try {
        std::cout << Dividend / Divisor;
    } catch(...) {
        std::cout << "Error.";
    }
    return 0;
}

Но приложение все равно сработает (хотя я поместил опцию -fexceptions из MinGW).

Можно ли поймать такое исключение (которое я понимаю не как исключение С++, а исключение FPU)?

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

Я делаю эти тесты на компьютере WindowsXP, но хотел бы сделать это кросс-платформой.

Ответ 1

Это не исключение. Это ошибка, которая определяется на уровне аппаратного и возвращается обратно в операционную систему, которая затем уведомляет вашу программу каким-то конкретным способом ОС (например, путем убийства процесс).

Я верю, что в этом случае происходящее не является исключением, а сигналом. Если это так: операционная система прерывает ваш основной поток управления программой и вызывает обработчик сигнала, который, в свою очередь, завершает работу вашей программы.

Это тот же тип ошибки, который появляется при разыменовании нулевого указателя (тогда ваша программа вылетает по сигналу SIGSEGV, ошибка сегментации).

Вы можете попытаться использовать функции из заголовка <csignal>, чтобы попытаться предоставить настраиваемый обработчик для сигнала SIGFPE (это для исключений с плавающей запятой, но это может быть так, что он также поднят для целочисленного деления на ноль - я Здесь действительно неуверенно). Однако вы должны заметить, что обработка сигнала зависит от ОС и MinGW каким-то образом "эмулирует" сигналы POSIX в среде Windows.


Здесь тест на MinGW 4.5, Windows 7:

#include <csignal>
#include <iostream>

using namespace std;

void handler(int a) {
    cout << "Signal " << a << " here!" << endl;
}

int main() {
    signal(SIGFPE, handler);
    int a = 1/0;
}

Вывод:

Сигнал 8 здесь!

И сразу после выполнения обработчика сигнала система уничтожает процесс и выводит сообщение об ошибке.

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

(Есть некоторые полезные сигналы, которые очень полезны вообще при низкоуровневом программировании и не приводят к тому, что ваша программа будет убита сразу после обработчика, но это глубокая тема).

Ответ 2

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

Вы можете использовать Structured Exception Handling, чтобы поймать деление на нулевую ошибку. Как это достигается, зависит от вашего компилятора. MSVC предлагает функцию catch Structured Exceptions как catch(...), а также предоставляет функцию перевода структурированных исключений в обычные исключения, а также предлагает __try/__except/__finally. Однако я недостаточно знаком с MinGW, чтобы рассказать вам, как это сделать в этом компиляторе.

Ответ 3

  • Нет стандартный способ ловли разделение на ноль от CPU.

  • Не преждевременно "оптимизировать" филиал. Ваше приложение действительно ли это связано с процессором? Я в этом сомневаюсь, и это не если вы нарушите свой код. В противном случае я мог бы сделать ваш код еще быстрее:

    int main(int argc, char *argv[]) { /* Fastest program ever! */ }
    

Ответ 5

Как-то реальное объяснение все еще отсутствует.

Можно ли поймать такое исключение (которое я понимаю не как исключение С++, а исключение FPU)?

Да, ваш блок catch должен работать на некоторых компиляторах. Но проблема в том, что ваше исключение не является исключением FPU. Вы выполняете целочисленное деление. Я не знаю, является ли это также захватывающей ошибкой, но это не исключение FPU, которое использует функцию представления IEEE чисел с плавающей запятой.

Ответ 6

В Windows (с Visual С++) попробуйте следующее:

BOOL SafeDiv(INT32 dividend, INT32 divisor, INT32 *pResult)
{
    __try 
    { 
        *pResult = dividend / divisor; 
    } 
    __except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ? 
             EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
    { 
        return FALSE;
    }
    return TRUE;
}

MSDN: http://msdn.microsoft.com/en-us/library/ms681409(v=vs.85).aspx

Ответ 7

Хорошо, если была обработка исключений, некоторые компоненты действительно нуждались в проверке. Поэтому вы ничего не теряете, если сами проверяете. И не так много быстрее, чем простой оператор сравнения (одна команда CPU "прыгает, если равна нулю" или что-то в этом роде, не помню имя)

Ответ 8

Чтобы избежать бесконечного "сигнала 8 здесь!" сообщения, просто добавьте 'exit' в красивый код Kos:

#include <csignal>
#include <iostream>
#include <cstdlib> // exit

using namespace std;

void handler(int a) {
    cout << "Signal " << a << " here!" << endl;
    exit(1);
}

int main() {
    signal(SIGFPE, handler);
    int a = 1/0;
}

Ответ 9

Как говорили другие, это не исключение, оно просто генерирует NaN или Inf.

Нулевой делитель - это только один способ сделать это. Если вы делаете много математики, есть много способов, например, log(not_positive_number), exp(big_number) и т.д.

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

В MSVC есть файл заголовка #include <float.h>, содержащий функцию _finite(x), которая указывает, является ли число конечным. Я почти уверен, что у MinGW есть что-то подобное. Вы можете проверить это после вычисления и бросить/поймать свое собственное исключение или что-то еще.