繁体   English   中英

如何知道引起异常的确切代码行?

[英]How to know the exact line of code where an exception has been caused?

如果我自己生成异常,我可以在异常中包含任何信息:一些代码行和源文件的名称。 像这样:

throw std::exception("myFile.cpp:255");

但是,未处理的异常或不是我生成的异常是怎么回事?

更好的解决方案是使用自定义类和宏。 :-)

#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>

class my_exception : public std::runtime_error {
    std::string msg;
public:
    my_exception(const std::string &arg, const char *file, int line) :
    std::runtime_error(arg) {
        std::ostringstream o;
        o << file << ":" << line << ": " << arg;
        msg = o.str();
    }
    ~my_exception() throw() {}
    const char *what() const throw() {
        return msg.c_str();
    }
};
#define throw_line(arg) throw my_exception(arg, __FILE__, __LINE__);

void f() {
    throw_line("Oh no!");
}

int main() {
    try {
        f();
    }
    catch (const std::runtime_error &ex) {
        std::cout << ex.what() << std::endl;
    }
}

似乎每个人都在尝试改进您的代码以在您的代码中抛出异常,但没有人在尝试您提出的实际问题。

这是因为它无法完成。 如果抛出异常的代码仅以二进制形式呈现(例如,在 LIB 或 DLL 文件中),则行号消失,并且无法将对象连接到源代码中的行。

有几种可能性可以找出抛出异常的位置:

使用编译器宏

在 throw 位置使用__FILE____LINE__宏(正如其他评论者已经展示的那样),通过在标准异常中将它们用作文本,或作为自定义异常的单独参数:

要么使用

throw std::runtime_error(msg " at " `__FILE__` ":" `__LINE__`);

或扔

class my_custom_exception {
  my_custom_exception(const char* msg, const char* file, unsigned int line)
...

请注意,即使针对 Unicode 进行编译(在 Visual Studio 中), FILE也会扩展为单字节字符串。 这适用于调试和发布。 不幸的是,带有代码抛出异常的源文件名被放置在输出可执行文件中。

栈走

通过遍历调用堆栈找出异常位置。

  • 在带有 gcc 的 Linux 上,函数 backtrace() 和 backtrace_symbols() 可以获得有关当前调用堆栈的信息。 请参阅gcc 文档如何使用它们。 必须使用 -g 编译代码,以便将调试符号放置在可执行文件中。

  • 在 Windows 上,您可以使用 dbghelp 库及其函数 StackWalk64 遍历堆栈。 有关详细信息,请参阅 Jochen Kalmbach 关于 CodeProject 的文章 这适用于调试和发布,你需要为所有你想要信息的模块发送 .pdb 文件。

您甚至可以通过在抛出自定义异常时收集调用堆栈信息来组合这两种解决方案。 调用堆栈可以存储在异常中,就像在 .NET 或 Java 中一样。 请注意,在 Win32 上收集调用堆栈非常慢(我的最新测试显示每秒收集大约 6 个调用堆栈)。 如果您的代码抛出许多异常,这种方法会大大降低您的程序速度。

最简单的解决方案是使用宏:

#define throw_line(msg) \
    throw std::exception(msg " " __FILE__ ":" __LINE__)

void f() {
    throw_line("Oh no!");
}

到目前为止还没有人提到提升。 如果您使用的是 boost c++ 库,它们确实为此提供了一些不错的异常默认值:

#include <boost/exception/diagnostic_information.hpp>
#include <exception>
#include <iostream>

struct MyException : std::exception {};

int main()
{
  try
  {
    BOOST_THROW_EXCEPTION(MyException());
  }
  catch (MyException &ex)
  {
    std::cerr << "Unexpected exception, diagnostic information follows:\n"
              << boost::current_exception_diagnostic_information();
  }
  return 0;
}

然后你可能会得到类似的东西:

Unexpected exception, diagnostic information follows:
main.cpp(10): Throw in function int main()
Dynamic exception type: boost::exception_detail::clone_impl<boost::exception_detail::error_info_injector<MyException> >
std::exception::what: std::exception

文档: https ://www.boost.org/doc/libs/1_63_0/libs/exception/doc/diagnostic_information.html

如果你有一个调试版本并在 Visual Studio 调试器中运行它,那么当任何类型的异常被抛出时,你都可以在它传播到世界之前中断调试器。

使用Debug > Exceptions菜单选项启用此功能,然后选中您感兴趣的异常类型。

如果应用程序源代码是您自己的,您还可以添加创建转储文件的功能。 例如,使用特定构建的转储文件和 PDB 文件(符号),您将获得 WinDbg 的堆栈跟踪。

受 Frank Krueger 的回答和std::nested_exception文档的启发,我意识到您可以将我已经使用了一段时间的 Frank 的回答与 std::nested_exception 结合起来,以创建包含文件和行信息的完整错误堆栈跟踪. 例如在我的实现中,运行

#include "Thrower.h"
#include <iostream>
// runs the sample function above and prints the caught exception
int main ( )
{
    try {
        // [Doing important stuff...]
        try {
            std::string s = "Hello, world!";
            try {
                int i = std::stoi ( s );
            }
            catch ( ... ) {
                thrower ( "Failed to convert string \"" + s + "\" to an integer!" );
            }
        }
        catch ( Error& e ) {
            thrower ( "Failed to [Do important stuff]!" );
        }
    }
    catch ( Error& e ) {
        std::cout << Error::getErrorStack ( e );
    }
    std::cin.get ( );
}

产出

ERROR: Failed to [Do important stuff]!
@ Location:c:\path\main.cpp; line 33
 ERROR: Failed to convert string "Hello, world!" to an integer!
 @ Location:c:\path\main.cpp; line 28
  ERROR: invalid stoi argument

这是我的实现:

#include <sstream>
#include <stdexcept>
#include <regex>

class Error : public std::runtime_error
{
    public:
    Error ( const std::string &arg, const char *file, int line ) : std::runtime_error( arg )
    {
        loc = std::string ( file ) + "; line " + std::to_string ( line );
        std::ostringstream out;
        out << arg << "\n@ Location:" << loc;
        msg = out.str( );
        bareMsg = arg;      
    }
    ~Error( ) throw() {}

    const char * what( ) const throw()
    {
        return msg.c_str( );
    }
    std::string whatBare( ) const throw()
    {
        return bareMsg;
    }
    std::string whatLoc ( ) const throw( )
    {
        return loc;
    }
    static std::string getErrorStack ( const std::exception& e, unsigned int level = 0)
    {
        std::string msg = "ERROR: " + std::string(e.what ( ));
        std::regex r ( "\n" );
        msg = std::regex_replace ( msg, r, "\n"+std::string ( level, ' ' ) );
        std::string stackMsg = std::string ( level, ' ' ) + msg + "\n";
        try
        {
            std::rethrow_if_nested ( e );
        }
        catch ( const std::exception& e )
        {
            stackMsg += getErrorStack ( e, level + 1 );
        }
        return stackMsg;
    }
    private:
        std::string msg;
        std::string bareMsg;
        std::string loc;
};

// (Important modification here)
// the following gives any throw call file and line information.
// throw_with_nested makes it possible to chain thrower calls and get a full error stack traceback
#define thrower(arg) std::throw_with_nested( Error(arg, __FILE__, __LINE__) )

```

我找到了 2 个解决方案,但都不是完全令人满意:

  1. 如果您调用std::set_terminate ,您可以从那里打印来自第三方异常抛出的调用堆栈。 不幸的是,没有办法从终止处理程序中恢复,因此您的应用程序将会终止。

  2. 如果你调用std::set_unexpected ,那么你需要使用throw(MyControlledException)从你的函数中声明尽可能多的,这样当它们由于第三方调用的函数而抛出时,你的unexpected_handler将能够给你一个细粒度的你的应用程序扔在哪里的想法。

我认为堆栈跟踪应该让你明白这一点。

在调试模式下编译您的软件并使用 valgrind 运行它。 它主要用于查找内存泄漏,但它也可以向您显示发生异常的确切位置valgrind --leak-check=full /path/to/your/software

其他人已经建议使用宏和可能的自定义类。 但是如果你有一个异常层次结构,你还需要在抛出时指定异常类型:

#define THROW(ExceptionType, message)                                    \
    throw ExceptionType(std::string(message) + " in " + __FILE__ + ':'   \
                        + std::to_string(__LINE__) + ':' + __func__)

THROW(Error, "An error occurred");

这里的假设是所有异常都接受一个字符串参数,这不是限制性的,因为可以将其他参数转换为字符串(例如使用std::to_string() )并将它们连接成一个字符串。

除了按照 Frank Krueger 的建议,针对您自己的异常使用带有宏的自定义类之外,您可能有兴趣查看结构化异常处理机制(您是在 Windows 下编程,对吗?)
检查MSDN 上的结构化异常处理

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM