捕获异常:除以零
Catching exception: divide by zero
当我尝试除以 0 时,以下代码没有捕获异常。我是否需要引发异常,或者计算机是否在运行时自动引发异常?
int i = 0;
cin >> i; // what if someone enters zero?
try {
i = 5/i;
}
catch (std::logic_error e) {
cerr << e.what();
}
您需要自己检查并引发异常。整数除以零在标准C++中也不例外。浮点除/余数也不是零,但至少具有可能产生的特定有理值(例如各种NaN/Inf
值(。
ISO C++20
标准(本答案中使用的迭代([stdexcept.syn]
部分中列出的例外情况是:
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
(毕竟,这是输入值的问题(是指示除以零的理想选择。
但是,第 [expr.mul]
节特别指出(对于整数和浮点除法以及整数余数(:
如果
/
或%
的第二个操作数为零,则行为未定义。
因此,它可能会引发这些(或任何其他(异常。它还可以格式化您的硬盘并嘲笑:-(
<小时 />如果你想实现这样的野兽,你可以在下面的程序中使用类似intDivEx
的东西(使用溢出变体(:
#include <iostream>
#include <stdexcept>
// Integer division/remainder, 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;
}
inline int intModEx (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, 0);
} catch (std::overflow_error &e) {
std::cout << e.what() << " -> ";
}
std::cout << i << std::endl;
try {
i = intDivEx (10, 2);
} catch (std::overflow_error &e) {
std::cout << e.what() << " -> ";
}
std::cout << i << std::endl;
return 0;
}
这输出:
Divide by zero exception -> 42
5
您可以看到它抛出并捕获除以零大小写的异常(保持返回变量不变(。
使用来自 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
视觉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();
}
}
当然,您可以跳过所有C++11左右的内容,并将它们放在传统的RAII管理结构中。
我所知C++规范没有提到除以零排除的任何内容。我相信你需要自己做...
Stroustrup在"C++的设计与演变"(Addison Wesley,1994(中说:"低级事件,如算术溢出和除以零,被假定由专用的低级机制处理,而不是由异常处理。这使C++能够在算术方面与其他语言的行为相匹配。它还避免了在大量流水线架构上发生的问题,在这些架构中,除以零等事件是异步的。`
使用关键字手动引发异常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;
}
您应该检查是否i = 0
,然后不要除法。
(可选(在检查后,您可以抛出异常并稍后处理(。
更多信息: http://www.cprogramming.com/tutorial/exceptions.html
setjmp
+ longjmp
https://stackoverflow.com/a/25601100/895245 提到了从信号处理程序引发C++异常的可能性,但是从信号处理程序中抛出异常提到了几个警告,所以我会非常小心。
作为另一种潜在的危险可能性,您还可以尝试使用较旧的 C setjmp
+ longjmp
机制,如下所示: C 处理信号 SIGFPE 并继续执行
主.cpp
#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/dtor(不兼容。 :(您可能会因此而获得资源泄漏。
所以你也必须非常非常小心。
我想寓意是信号处理程序很难,除非你确切地知道自己在做什么,否则你应该尽可能避免它们。
检测浮点零除法
也可以通过 glibc 调用来检测浮点除以零:
#include <cfenv>
feenableexcept(FE_INVALID);
如所示:安静 NaN 和信令 NaN 有什么区别?
这使得它像整数除以零一样提高 SIGFPE,而不仅仅是静默地 qnan 和设置标志。
这个呢?用Clang测试,GCC投掷了SIGILL。
#include <iostream>
#include <cassert>
int main()
{
unsigned int x = 42;
unsigned int y = x;
y -= x;
x /= y;
std::cout << x << " != "<< *(&x) << std::endl;
assert (*(&x) == x);
}
如果你想捕获涉及整数的除以零错误(它对浮点数有效(,而不必通过抛出来触发它,你应该使用这样的信号处理程序:
void signalHandler( int signum ){
//Division by 0 is c++ signal #8 (signum = 8).
cout << "Interrupt signal (" << signum << ") received.n";
exit(signum);
}
然后将此函数定义为可能发生除以零的代码之前的信号处理程序,如下所示:
int main(){
...
signal(SIGFPE, signalHandler);
...
}
请注意,SIGFPE 是无效算术运算的信号。
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
}
你可以只做assert(2 * i != i)
这将抛出一个断言。如果您需要更花哨的东西,您可以编写自己的异常类。
- 当类定义不可见时捕获异常
- 来自 Android 应用程序内部的 boost 类型的 boost::wrapexcept<boost::system::system_error> 的未捕获异常
- 如何通过 pybind11 从 python 中的C++中捕获异常?
- 信号后未捕获异常
- 捕获异常后如何退出程序执行
- C++ 捕获异常后进行清理的标准方法是什么?
- 使用模板类引发和捕获异常
- E/libc++abi:终止于类型为google::protobuf::FatalException的未捕获异常
- 如果在生成 std::thread 后引发,则未捕获异常
- C++ 未捕获异常,程序将终止并中止
- C++程序在第一次尝试时会给出垃圾,但如果它捕获异常并重试,则会给出适当的值
- 仅捕获异常就可以检测所有二进制文件在C 中读取错误是否足够
- 如何捕获 I/O 异常(确切地说是 I/O,而不是 std::exception)
- 为什么捕获异常播放允许尾括号
- throw() 函数应该总是在异常时展开堆栈并允许捕获异常还是必须调用 std::terminate ?
- 寻求与类型为 std::invalid_argument 的未捕获异常相关的运行时错误的建议: stoi:无转换
- 如何从调用函数中捕获异常
- 当用户在字符数组中输入整数值时捕获异常
- C++按值捕获异常时的示例是不好的
- 使用 -O2 或 -O3 标志编译时未捕获异常