c++如何设置更严格的异常(错误)规则

c++ How to set more strict exception(error) rules?

本文关键字:异常 错误 规则 设置 c++ 何设置      更新时间:2023-10-16

此代码不会导致异常:

int *a=new int[100];  
try{
  a[101]=1;
}
catch(...){
//never called
}

但是,如果索引较大(a[11111111]=1),则会出现错误。

是否可以制定更严格的错误处理规则?(MSDN VC++)

谢谢!

不幸的是,C++的设计理念是程序员永远不会出错。别笑。。。我是认真的。

在C++中,没有"运行时错误天使"可以确保代码不会做任何愚蠢的事情,比如访问边界外的数组元素或两次释放同一指针。主要的假设是程序员永远不会编写这样的代码,因此浪费CPU时间检查访问上的数组边界不是一个好主意。

这些是标准库抛出的异常,但在程序员不可能知道操作是否可行的情况下(比如试图分配比可用内存更多的内存)。

在其他情况下,假设代码是正确的,不需要检查。然而,作为一种简单的工具,标准库中有一些方法使用异常机制来报告故障,如std::vector::atdynamic::cast

C++没有"运行时错误天使",而是有"未定义的行为守护程序"(也称为"鼻守护程序"),如果你违反了任何规则,它们可以做任何事情,而不是指向你的问题。作为恶性守护进程,他们喜欢应用的最危险的行为是在黑暗中保持沉默,让程序运行,以提供合理的结果。。。除非你在包括你父母和老板在内的广大观众面前运行这个程序(在这种情况下,他们会让屏幕看起来最让你尴尬的)。

C++的梦想是,在编译时移动所有有效性检查将使程序免于运行时错误。尽管这是一种明显的错觉,但这正是该语言的设计初衷;用C++编程的唯一方法是避免所有错误(目标不那么容易,因为语言非常复杂)。

雪上加霜的是,微软决定,当一个程序做得如此糟糕,甚至操作系统本身都能看到这是一个明显的错误时,报告机制仍然应该基于异常抛出。这允许编写程序,如果程序访问的内存甚至不在其地址空间内,则程序不会因崩溃而退出。捕捉甚至吞下(!)这些错误的能力是永远不应该使用的,除非你对"健壮程序"的定义是"难以杀死的程序"。

这就是你的catch(...)正在做的。。。

您似乎对异常有两个误解:

  1. C++为所有错误抛出异常,比如Java
  2. C++应该由程序员编写,这样所有错误都会引发异常

两者确实都是误解。在C++中,标准库组件或语言本身很少抛出异常,只有在问题很少但超出您控制范围的情况下,或者没有其他简单的方法报告错误的情况下才应该抛出异常。

前者的一个例子是分配的内存超过了系统所能提供的内存。对于无法防止的外部资源,这是一个相对罕见的问题。因此,如果内存不足,new确实会抛出std::bad_alloc异常。

后者的例子是构造函数和带有引用的dynamic_cast

访问动态分配数组中的非法索引是而不是异常的良好用例,因为您可以轻松防止该错误。如果有什么不同的话,它应该是一个断言,因为如果你访问了一个非法的索引,那么你就有了一个编程错误,即bug无法在运行时修复Bug

处理此类低级错误的常用C++方法是未定义行为。在您的示例中:

int *a=new int[100];  
try{
  a[101]=1;
}
catch(...){
//never called
}

尝试访问a[101]会调用未定义的行为,这意味着程序可以执行所有操作。它可能崩溃,可能正常工作,甚至可能抛出异常。这完全取决于编译器和环境。

当然,生活在不明确的行为中不是一件好事,所以你想做点什么是可以理解的

首先,永远不要使用new[]。请改用std::vectorstd::vector的非法元素访问仍然是未定义的行为,但编译器可能会引入额外的运行时检查,从而导致未定义行为立即崩溃,从而修复错误。

std::vector也有一个at成员函数,它在错误的索引上抛出异常,但这更像是一个设计错误,因为在std::vector级别,错误的索引表示程序逻辑中更高抽象级别的问题。


重要的是,你放弃了可以平等地"处理"所有错误的想法。将您的错误分为三类:

  1. 错误错误意味着你的代码是错误的,你的程序不是你想象的那样。对这些错误使用assert和/或依靠编译器在C++标准库组件中引入相应的检查。错误的代码应该很快崩溃,这样它可以造成尽可能小的伤害。提示:MSVC有一种叫做"调试版本"的东西。"调试版本"是一组编译器和链接器选项,它允许进行大量额外的运行时检查,以帮助您查找代码中的错误。

  2. 输入错误所有程序都收到错误的输入。您决不能认为输入是正确的。输入来自人类还是来自机器并不重要。错误的输入必须是正常程序流的一部分。不要将assert或异常用于错误的输入。

  3. 外部资源的异常状态这就是应该使用异常的原因。每个程序都以某种形式依赖于外部资源,通常由操作系统提供。主要的外部资源是内存。像std::vector<int> x(100);这样的东西通常不会失败,但理论上可能会失败,因为它需要记忆。启动一个新线程通常不会失败,但理论上,操作系统可能无法启动一个。这些都是例外情况,所以例外是处理它们的好方法。

当然,这些是粗略的指导方针。在错误的输入和外部资源的问题之间特别难以划清界限。

尽管如此,这里还是有一个试图总结指导方针的例子:

#include <iostream>
#include <vector>
#include <cstdlib>
#include <cassert>
void printElement(std::vector<int> const& v, int index)
{
    // ----------------
    // Error category 1
    // ----------------
    assert(index >= 0);
    assert(index < static_cast<int>(v.size()));
    std::cout << v[index] << "n";
}
int main()
{
    std::cout << "Enter size (10-100): ";
    int size = 0;
    std::cin >> size;
    if (!std::cin || (size < 10) || (size > 100))
    {
        // ----------------
        // Error category 2
        // ----------------
        std::cerr << "Wrong inputn";
        return EXIT_FAILURE;
    }
    try
    {
        std::vector<int> v(size);
        printElement(v, 0);
    }
    catch (std::bad_alloc const&)
    {
        // ----------------
        // Error category 3
        // ----------------
        std::cerr << "Out of memoryn";
        return EXIT_FAILURE;
    }
}

您应该使用std::vector及其方法at(int index)来保证数组边界检查:

#include <iostream> 
#include <vector>
int main()
{
    std::vector<int> a(100);
    try{
        int index = 101;
        a.at(index) = 1;
        std::cout << a.at(index) << std::endl;
    }
    catch (const std::out_of_range& ex) {
        std::cerr << "Out of Range error: " << ex.what() << std::endl;
    }
    return 0;
}

您的问题的一个答案是C++不会阻止您编写有害代码。您的代码正在写入一个您一无所知的内存空间。当您将索引设置得很高时,您可能会写入应用程序不允许接触的位置,这就是它失败的原因。

在MS Visual C++中,您可以设置不同的警告级别,其中一些警告级别会警告您不安全的代码,因此您可以在发货前修复它/使其更安全。

您也可以尝试其他工具来检查您的代码。(我使用过:http://cppcheck.sourceforge.net/)