C++ 如何知道导致异常的确切代码行?

C++ 如何知道导致异常的确切代码行?,c++,exception,C++,Exception,如果我自己生成一个异常,我可以在异常中包含任何信息:代码行的数量和源文件的名称。大概是这样的: throw std::exception("myFile.cpp:255"); 但是,对于未处理的异常或不是我生成的异常,又有什么关系呢?我认为堆栈跟踪应该能让您直截了当。如果您有一个调试生成并在Visual Studio调试器中运行它,那么您可以在抛出任何类型的异常时,在异常传播到世界之前,闯入调试器 使用Debug>Exceptions菜单选项启用此选项,然后选中标记您感兴趣的异常类型 如果应用

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

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

但是,对于未处理的异常或不是我生成的异常,又有什么关系呢?

我认为堆栈跟踪应该能让您直截了当。

如果您有一个调试生成并在Visual Studio调试器中运行它,那么您可以在抛出任何类型的异常时,在异常传播到世界之前,闯入调试器

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


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

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

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

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

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

#包括
#包括
#包括
#包括
类my_异常:public std::runtime_错误{
std::字符串msg;
公众:
my_异常(常量std::string&arg,常量char*文件,int行):
std::运行时错误(arg){
std::ostringstream o;

o除了像Frank Krueger建议的那样,为您自己的异常使用带有宏的自定义类之外,您可能有兴趣了解结构化异常处理机制(您正在windows下编程,对吗?

检查

有几种方法可以找出引发异常的位置:

使用编译器宏

在抛出位置(如其他注释者所示)使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu文件
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu行
宏,或者在std异常中将它们作为

任用

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中)时,文件也会扩展为单字节字符串。 这在debug和release中有效。不幸的是,带有代码抛出异常的源文件名被放置在输出可执行文件中

堆叠行走

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

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

  • 在Windows上,您可以使用dbghelp库及其函数StackWalk64遍历堆栈。有关详细信息,请参阅Jochen Kalmbach关于CodeProject的文章。这在调试和发行版中有效,并且您需要为所有需要相关信息的模块提供.pdb文件


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

似乎每个人都在努力改进您的代码,以便在代码中抛出异常,而没有人试图回答您提出的实际问题


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

我找到了两种解决方案,但都不完全令人满意:

  • 如果调用
    std::set_terminate
    ,则可以从第三方异常抛出处打印调用堆栈。不幸的是,无法从终止处理程序恢复,因此应用程序将死亡

  • 如果调用
    std::set_unexpected
    ,则需要使用
    throw(MyControlledException)从函数中声明尽可能多的值
    ,这样当它们由于第三方调用的函数而抛出时,您的
    意外\u处理程序将能够为您提供应用程序抛出位置的细粒度概念


  • 受Frank Krueger的答案和的文档的启发,我意识到您可以将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 ( );
    }
    
    以下是我的实现:

    #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__) )
    
    #包括
    #包括
    #包括
    类错误:public std::runtime\u错误
    {
    公众:
    错误(常量std::string&arg,常量char*文件,int行):std::runtime\u错误(arg)
    {
    loc=std::字符串(文件)+“行”+std::to_字符串(行);
    std::ostringstream out;
    
    OUT

    到目前为止还没有人提到Boost。如果你使用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;
    }
    
    #包括
    #包括
    #包括
    结构MyException:std::exception{};
    int main()
    {
    尝试
    {
    BOOST_THROW_异常(MyException());
    }
    捕获(MyException和ex)
    {
    
    std::cerr在调试模式下编译您的软件,并使用valgrind运行。它主要用于查找内存泄漏,但也可以向您显示异常发生的确切位置
    valgrind--leak check=full/path/to/your/software

    其他人已经建议使用宏,可能还建议使用自定义类。但如果您有异常,请使用在层次结构中,您还需要在引发时指定异常类型:

    #定义抛出(例外类型,消息)\
    在“+”文件“+”中抛出异常类型(std::string(message)+”:”\
    +std::to_string(uuu LINE_uuu)+':'+uu func_uu)
    抛出(错误,“发生错误”);
    
    这里的假设是,所有异常都接受一个字符串参数,而不是
    #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__) )
    
    #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