关于c ++:如何知道导致异常的确切代码行?

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

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

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#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__宏(如其他注释所示),或者在std异常中使用它们作为文本,或者作为自定义异常的单独参数:

要么使用

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

或投掷

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

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

栈走

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

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

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

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


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

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

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


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

1
2
3
4
5
6
#define throw_line(msg) \
    throw std::exception(msg"" __FILE__":" __LINE__)


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


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

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

  • 如果您调用std::set_unexpected,那么您需要尽可能多地从带有throw(MyControlledException)的函数中声明,以便当它们由于第三方调用的函数而抛出时,您的unexpected_handler将能够给您一个关于应用程序抛出位置的细粒度概念。


  • 我认为一个堆栈跟踪应该能让你找到重点。


    受STD:NeStdEdExpRead的Frank Krueger的回答和文档启发,我意识到,您可以结合弗兰克的答案,我用了一段时间,用STD::NeestddExExter创建一个完整的错误堆栈跟踪文件和行信息。例如,在我的实现中,运行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #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 ( );
    }

    输出

    1
    2
    3
    4
    5
    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

    以下是我的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #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 <<"
    @ 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 ("
    "
    );
            msg = std::regex_replace ( msg, r,"
    "
    +std::string ( level, ' ' ) );
            std::string stackMsg = std::string ( level, ' ' ) + msg +"
    "
    ;
            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__) )

    ` `


    除了使用一个自定义类和一个宏(如Frank Krueger建议的那样),对于您自己的异常,您可能会有兴趣看看结构化的异常处理机制(您是在Windows下编程的,对吧?)检查msdn上的结构化异常处理