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

Posted

技术标签:

【中文标题】如何知道导致异常的确切代码行?【英文标题】:How to know the exact line of code where an exception has been caused? 【发布时间】:2008-12-08 07:09:26 【问题描述】:

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

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

但是未处理的异常或不是我生成的异常是什么?

【问题讨论】:

小注:这不是符合标准的代码; std::exception 没有接受字符串的 ctor。但是 MSVC 确实(错误地)允许它...... 确实如此。你应该扔runtime_error; “例外”什么也没说。 ***.com/questions/3026649/… 【参考方案1】:

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

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

【讨论】:

我一般不喜欢宏,但这是很好的宏使用示例。 FIELINE 非常方便快速查找错误。 +1。 推荐的做法是在 what() 函数而不是构造函数中执行任何可能分配内存的操作(例如字符串流操作)。原因是在捕获点(一旦堆栈展开一点)可能有比抛出点更多的可用资源。 您可能想使用: std::runtime_error(std::string(file)+":"+std::to_string(line)+":"+arg) 来避免ostream 操作。 这仅在您使用自己的代码时才有效。问题表明它们不是。【参考方案2】:

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

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

【讨论】:

有没有办法在引发异常时打印下一条指令的地址?如果我们可以有指令地址,我们可以很容易地得到代码行。 如果您想要堆栈跟踪和其他 21 世纪的功能,请使用 Java。或.Net。 C++ 本来就很困难。 @Tuntable:这并不是“难上加难”。一个更好的说法是“它不是为了舒适而建造的;它是为了速度而建造的”。 @JamesCurran 然后完成了一项非常糟糕的工作,因为原始 C 只是快一点点,而且舒适度更低。显然这不是主要目标。 @Kaihaku Raw C 确实更快,但在任何语言中都需要在开发时间和速度之间进行权衡。当涉及到异常时,C++ 追求速度是一个有效的权衡。这就是为什么 James Curran 的回答既是最正确的,也不一定是最有帮助的。【参考方案3】:

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

使用编译器宏

在抛出位置使用 __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 个调用堆栈)。如果您的代码抛出许多异常,这种方法会大大降低您的程序速度。

【讨论】:

【参考方案4】:

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

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

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

【讨论】:

我认为问题的重点是异常是由其他地方的代码引发的,而不是在原始发布者的控制之下?【参考方案5】:

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

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

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

【讨论】:

【参考方案6】:

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

【讨论】:

【参考方案7】:

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

```

【讨论】:

【参考方案8】:

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

【讨论】:

只有当他可以闯入调试器或者他有一个带有符号的转储文件时,他才会得到一个堆栈跟踪。 能够分析转储文件非常有用。我认为发帖者应该克服他不愿意使用调用堆栈和故障转储的问题,亲自动手,并为事后分析生成小型转储。【参考方案9】:

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

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

    1234563让您详细了解应用程序抛出的位置。

【讨论】:

【参考方案10】:

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

【讨论】:

【参考方案11】:

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

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

THROW(Error, "An error occurred");

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

【讨论】:

【参考方案12】:

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

【讨论】:

以上是关于如何知道导致异常的确切代码行?的主要内容,如果未能解决你的问题,请参考以下文章

我怎么知道引发异常的确切命令?

错误:“行/列不存在数据”使用 OdbcDataReader

ShiroShiro1.5.0更新问题导致IniSecurityManagerFactory异常

ShiroShiro1.5.0更新问题导致IniSecurityManagerFactory异常

什么时候要catch并抛出异常

需要确切知道这段代码中发生了啥