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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/348833/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:49:57  来源:igfitidea点击:

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

c++exception

提问by Mar

If I generate an exception on my own, I can include any info into the exception: a number of code line and name of source file. Something like this:

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

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

But what's with unhandled exceptions or with exceptions that were not generated by me?

但是未处理的异常或不是由我生成的异常怎么办?

采纳答案by James Curran

It seems everyone is trying to improve your code to throw exceptions in your code, and no one is attempting the actual question you asked.

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

Which is because it can't be done. If the code that's throwing the exception is only presented in binary form (e.g. in a LIB or DLL file), then the line number is gone, and there's no way to connect the object to to a line in the source code.

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

回答by Frank Krueger

A better solution is to use a custom class and a macro. :-)

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

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

回答by vividos

There are several possibilities to find out where the exception was thrown:

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

Using compiler macros

使用编译器宏

Using __FILE__and __LINE__macros at throw location (as already shown by other commenters), either by using them in std exceptions as text, or as separate arguments to a custom exception:

在抛出位置使用__FILE____LINE__宏(其他评论者已经展示过),可以在 std 异常中将它们作为文本使用,或者作为自定义异常的单独参数使用:

Either use

要么使用

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

or throw

或扔

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

Note that even when compiling for Unicode (in Visual Studio), FILEexpands to a single-byte string. This works in debug and release. Unfortunately, source file names with code throwing exceptions are placed in the output executable.

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

Stack Walking

堆栈行走

Find out exception location by walking the call stack.

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

  • On Linux with gcc the functions backtrace() and backtrace_symbols() can get infos about the current call stack. See the gcc documentationhow to use them. The code must be compiled with -g, so that debug symbols are placed in the executable.

  • On Windows, you can walk the stack using the dbghelp library and its function StackWalk64. See Jochen Kalmbach's articleon CodeProject for details. This works in debug and release, and you need to ship .pdb files for all modules you want infos about.

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

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

You can even combine the two solutions by collecting call stack info when a custom exception is thrown. The call stack can be stored in the exception, just like in .NET or Java. Note that collecting call stack on Win32 is very slow (my latest test showed about 6 collected call stacks per second). If your code throws many exceptions, this approach slows down your program considerably.

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

回答by Johann Gerell

If you have a debug build and run it in the Visual Studio debugger, then you can break into the debugger when any kind of exception is thrown, before it propagates to the world.

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

Enable this with the Debug > Exceptionsmenu alternative and then check-marking the kinds of exceptions that you are interested in.

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

You can also add the ability to create a dump file, if the application source code is your own. With the dump file and PDB files (symbols) for the specific build, you'll get stacktraces with WinDbg, for example.

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

回答by Frank Krueger

The simplest solution is to use a macro:

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

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

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

回答by Marcin Gil

I think that a stack trace should get you to the point.

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

回答by Daniel Pinyol

I found 2 solutions, but neither is fully satisfactory:

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

  1. If you call std::set_terminate, you can from there print the callstack right from the third party exception throw. Unfortunately, there's no way to recover from a terminate handler, and hence your application will die.

  2. If you call std::set_unexpected, then you need to declare as many as possible from your functions with throw(MyControlledException), so that when they throw due to third party called functions, your unexpected_handlerwill be able to give you a fine-grained idea of where your application threw.

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

  2. 如果您调用std::set_unexpected,那么您需要从您的函数中声明尽可能多的 with throw(MyControlledException),以便当它们因第三方调用的函数而抛出时,您unexpected_handler将能够让您对应用程序抛出的位置有一个细粒度的了解。

回答by Dominic

No one mentioned boost so far. If you're using boost c++ libraries they do come with some nice exception defaults for this:

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

And then you might get something like:

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

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

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

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

回答by Spixmaster

Compile your software in debug mode and run it with valgrind. It is mainly for finding memory leaks but it can also show you the exact line of where exceptions occur valgrind --leak-check=full /path/to/your/software.

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

回答by aquirdturtle

Inspired by Frank Krueger's answer and the documentation for std::nested_exception, I realized that you can combine Frank's answer, which I've been using for a while, with std::nested_exception to create a full error stack trace with file & line info. For example with my implementation, running

受 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 ( );
}

outputs

产出

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

Here's my implementation:

这是我的实现:

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

```

``