Подтвердить что ты не робот

Исключение исключения: деление на ноль

Следующий код не вызывает исключения, когда я пытаюсь разделить на 0. Нужно ли мне бросать исключение или компьютер автоматически бросает его во время выполнения?

int i = 0;

cin >> i;  // what if someone enters zero?

try {
    i = 5/i;
}
catch (std::logic_error e) {

    cerr << e.what();
}
4b9b3361

Ответ 1

Вам нужно проверить это самостоятельно и выдать исключение. Целочисленное деление на ноль не является исключением в стандарте C++.

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

Исключения, перечисленные в стандарте ISO:

namespace std {
    class logic_error;
        class domain_error;
        class invalid_argument;
        class length_error;
        class out_of_range;
    class runtime_error;
        class range_error;
        class overflow_error;
        class underflow_error;
}

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

Однако в разделе 5.6 (из C++11, хотя я не думаю, что это изменилось по сравнению с предыдущей итерацией) конкретно говорится:

Если второй операнд / или % равен нулю, поведение не определено.

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


Если вы хотите реализовать такого зверя, вы можете использовать что-то вроде intDivEx в следующей программе (используя вариант переполнения):

#include <iostream>
#include <stdexcept>

// Integer division, catching divide by zero.

inline int intDivEx (int numerator, int denominator) {
    if (denominator == 0)
        throw std::overflow_error("Divide by zero exception");
    return numerator / denominator;
}

int main (void) {
    int i = 42;

    try { i = intDivEx (10, 2); }
    catch (std::overflow_error e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;

    try { i = intDivEx (10, 0); }
    catch (std::overflow_error e) {
        std::cout << e.what() << " -> ";
    }
    std::cout << i << std::endl;

    return 0;
}

Это выводит:

5
Divide by zero exception -> 5

и вы можете видеть, что он выдает и ловит исключение для деления на ноль.


% Эквивалент почти точно такой же:

// Integer remainder, catching divide by zero.

inline int intModEx (int numerator, int denominator) {
    if (denominator == 0)
        throw std::overflow_error("Divide by zero exception");
    return numerator % denominator;
}

Ответ 2

Обновлено с комментариями от ExcessPhase

GCC (по крайней мере версия 4.8) позволит вам эмулировать это поведение:

#include <signal.h>
#include <memory>
#include <iostream>

int main() {
    std::shared_ptr<void(int)> handler(
        signal(SIGFPE, [](int signum) {throw std::logic_error("FPE"); }),
        [](__sighandler_t f) { signal(SIGFPE, f); });

    int i = 0;

    std::cin >> i;  // what if someone enters zero?

    try {
        i = 5/i;
    }
    catch (std::logic_error e) {
        std::cerr << e.what();
    }
}

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

Вам нужно скомпилировать хотя бы эти параметры:

g++ -c Foo.cc -o Foo.o -fnon-call-exceptions -std=c++11

Visual C++ также позволит вам сделать нечто подобное:

#include <eh.h>
#include <memory>

int main() {
    std::shared_ptr<void(unsigned, EXCEPTION_POINTERS*)> handler(
        _set_se_translator([](unsigned u, EXCEPTION_POINTERS* p) {
            switch(u) {
                case FLT_DIVIDE_BY_ZERO:
                case INT_DIVIDE_BY_ZERO:
                    throw std::logic_error("Divide by zero");
                    break;
                ...
                default:
                    throw std::logic_error("SEH exception");
            }
        }),
        [](_se_translator_function f) { _set_se_translator(f); });

    int i = 0;

    try {
        i = 5 / i;
    } catch(std::logic_error e) {
        std::cerr << e.what();
    }
}

И, конечно, вы можете пропустить все 11 C++ 11-ишность этого и поместить их в традиционную структуру управления RAII.

Ответ 3

Насколько я знаю, спецификации С++ не упоминают ничего о делении на ноль. Я считаю, что вам нужно сделать это самостоятельно...

Страуструп говорит, что в "Проекте и эволюции С++" (Addison Wesley, 1994) "события низкого уровня, такие как арифметические переполнения и деление на ноль, предполагается, что обрабатывается специальным механизмом более низкого уровня, чем за исключениями, что позволяет С++ сопоставлять поведение других языков, когда дело касается арифметики, а также позволяет избежать проблем, возникающих на сильно конвейерных архитектурах, где такие события, как деление на ноль, являются асинхронными."

Ответ 4

Вы должны проверить, если i = 0 и не делить тогда.

(При желании после проверки вы можете сгенерировать исключение и обработать его позже).

Более подробная информация на: http://www.cprogramming.com/tutorial/exceptions.html

Ответ 5

Вам нужно выбросить исключение вручную с помощью ключевого слова throw.

Пример:

#include <iostream>
using namespace std;

double division(int a, int b)
{
   if( b == 0 )
   {
      throw "Division by zero condition!";
   }
   return (a/b);
}

int main ()
{
   int x = 50;
   int y = 0;
   double z = 0;

   try {
     z = division(x, y);
     cout << z << endl;
   }catch (const char* msg) {
     cerr << msg << endl;
   }

   return 0;
}

Ответ 6

setjmp + longjmp

fooobar.com/questions/192022/... упомянул возможность или выбрасывать исключение C++ из обработчика сигнала, но, выбрасывая исключение из обработчика сигнала, упоминает несколько предостережений об этом, поэтому я буду очень осторожен.

В качестве другой потенциально опасной возможности вы также можете попытаться использовать более старый механизм C setjmp + longjmp, как показано на рисунке: C обрабатывает сигнал SIGFPE и продолжает выполнение

#include <csetjmp>
#include <csignal>
#include <cstring>
#include <iostream>

jmp_buf fpe;

void handler(int signum) {
    longjmp(fpe, 1);
}

int main() {
    volatile int i, j;
    for(i = 0; i < 10; i++) {
        struct sigaction act;
        struct sigaction oldact;
        memset(&act, 0, sizeof(act));
        act.sa_handler = handler;
        act.sa_flags = SA_NODEFER | SA_NOMASK;
        sigaction(SIGFPE, &act, &oldact);
        if (0 == setjmp(fpe)) {
            std::cout << "before divide" << std::endl;
            j = i / 0;
            sigaction(SIGFPE, &oldact, &act);
        } else {
            std::cout << "after longjmp" << std::endl;
            sigaction(SIGFPE, &oldact, &act);
        }
    }
    return 0;
}

Скомпилируйте и запустите:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
./main.out

Выход:

i = 0
before divide
after longjmp
i = 1
before divide
after longjmp
i = 2
before divide
after longjmp

man longjmp говорит, что вы можете использовать longjmp из обработчиков сигналов, но с несколькими оговорками:

Техническое исправление POSIX.1-2008 2 добавляет longjmp() и siglongjmp() в список функций, безопасных для асинхронных сигналов. Тем не менее, стандарт рекомендует избегать использования этих функций от обработчиков сигналов и далее указывает, что если эти функции вызываются из обработчика сигналов, который прервал вызов не асинхронно-безопасной функции сигнала (или некоторого эквивалентного, такого в качестве шагов, эквивалентных exit (3), которые происходят при возврате из начального вызова main()), поведение не определено, если программа впоследствии выполняет вызов не асинхронно безопасной для сигнала функции. Единственный способ избежать неопределенного поведения - обеспечить одно из следующего:

  • После долгого перехода от обработчика сигнала программа не вызывает никаких функций, не асинхронно безопасных для сигнала, и не возвращает исходный вызов main().

  • Любой сигнал, чей обработчик выполняет длинный переход, должен быть заблокирован во время каждого вызова функции, не связанной с асинхронным сигналом, и никакие функции, не поддерживающие асинхронный сигнал, не вызываются после возврата из первоначального вызова main().

Смотрите также: Longjmp вне обработчика сигнала?

Однако, бросая исключение из обработчика сигнала, упоминает, что это еще более опасно для C++:

setjmp и longjmp не совместимы с исключениями и RAII (ctors/dtors). :( Вы, вероятно, получите утечки ресурсов с этим.

поэтому вы должны быть очень, очень осторожны с этим.

Полагаю, мораль в том, что обработчики сигналов сложны, и вам следует избегать их как можно чаще, если вы точно не знаете, что делаете.

Обнаружение деления с плавающей точкой на ноль

Также возможно обнаружить деление с плавающей точкой на ноль с помощью вызова glibc:

#include <cfenv>

feenableexcept(FE_INVALID);

как показано на: В чем разница между тихим NaN и сигнальным NaN?

Это заставляет его поднимать SIGFPE так же, как целочисленное деление на ноль, а не просто молча qnan и устанавливать флаги.

Ответ 7

do i need to throw an exception or does the computer automatically throws one at runtime?

Вам нужно throw исключение самостоятельно и catch его. например.

try {
  //...
  throw int();
}
catch(int i) { }

Или catch исключение, которое генерируется вашим кодом.

try {
    int *p = new int();
}
catch (std::bad_alloc e) {
    cerr << e.what();
}

В вашем случае я не уверен, существует ли стандартное исключение означает для деления на ноль. Если такого исключения нет, вы можете использовать

catch(...) {  // catch 'any' exception
}

Ответ 8

Вы можете просто сделать assert(2 * i != i), который будет вызывать assert. Вы можете написать свой собственный класс исключений, если вам нужно что-то более приятное.