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

时间:2008-12-08 07:09:27

标签: c++ exception

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

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

但是未处理的异常或者我没有生成的异常是什么?

12 个答案:

答案 0 :(得分:29)

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

#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;
    }
}

答案 1 :(得分:22)

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

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

答案 2 :(得分:20)

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

使用编译器宏

在throw位置使用__FILE____LINE__宏(如其他评论者所示),可以在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中)时, FILE 也会扩展为单字节字符串。  这适用于调试和发布。不幸的是,带有抛出异常代码的源文件名放在输出可执行文件中。

堆叠行走

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

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

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

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

答案 3 :(得分:3)

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

使用调试&gt;启用此功能例外菜单替代,然后检查标记您感兴趣的异常种类。

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

答案 4 :(得分:2)

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

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

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

答案 5 :(得分:1)

我认为堆栈跟踪可以帮助您达到目的。

答案 6 :(得分:1)

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

  1. 如果你致电std::set_terminate,你可以从那里打印出第三方异常抛出的callstack。不幸的是,没有办法从终止处理程序恢复,因此你的应用程序将会死亡。

  2. 如果你打电话给std::set_unexpected,那么你需要使用throw(MyControlledException)从你的函数中尽可能多地声明,这样当他们因第三方调用函数而抛出时,你的{{1}能够给你一个关于你的应用程序投掷位置的细粒度的想法。

答案 7 :(得分:0)

除了使用带有宏的自定义类(如Frank Krueger所建议),对于您自己的异常,您可能有兴趣看一下结构化异常处理机制(您在windows下编程,对吗?)<登记/> 查看Structured Exception Handling on MSDN

答案 8 :(得分:0)

受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__) )

```

答案 9 :(得分:0)

到目前为止,没有人提到助推器。如果您使用的是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

答案 10 :(得分:0)

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

答案 11 :(得分:0)

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

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

THROW(Error, "An error occurred");

这里的假设是所有异常都接受一个字符串参数。