簡體   English   中英

如何知道引起異常的確切代碼行?

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

如果我自己生成異常,我可以在異常中包含任何信息:一些代碼行和源文件的名稱。 像這樣:

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

但是,未處理的異常或不是我生成的異常是怎么回事?

更好的解決方案是使用自定義類和宏。 :-)

#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__宏(正如其他評論者已經展示的那樣),通過在標准異常中將它們用作文本,或作為自定義異常的單獨參數:

要么使用

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 文檔如何使用它們。 必須使用 -g 編譯代碼,以便將調試符號放置在可執行文件中。

  • 在 Windows 上,您可以使用 dbghelp 庫及其函數 StackWalk64 遍歷堆棧。 有關詳細信息,請參閱 Jochen Kalmbach 關於 CodeProject 的文章 這適用於調試和發布,你需要為所有你想要信息的模塊發送 .pdb 文件。

您甚至可以通過在拋出自定義異常時收集調用堆棧信息來組合這兩種解決方案。 調用堆棧可以存儲在異常中,就像在 .NET 或 Java 中一樣。 請注意,在 Win32 上收集調用堆棧非常慢(我的最新測試顯示每秒收集大約 6 個調用堆棧)。 如果您的代碼拋出許多異常,這種方法會大大降低您的程序速度。

最簡單的解決方案是使用宏:

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

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

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

如果你有一個調試版本並在 Visual Studio 調試器中運行它,那么當任何類型的異常被拋出時,你都可以在它傳播到世界之前中斷調試器。

使用Debug > Exceptions菜單選項啟用此功能,然后選中您感興趣的異常類型。

如果應用程序源代碼是您自己的,您還可以添加創建轉儲文件的功能。 例如,使用特定構建的轉儲文件和 PDB 文件(符號),您將獲得 WinDbg 的堆棧跟蹤。

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

```

我找到了 2 個解決方案,但都不是完全令人滿意:

  1. 如果您調用std::set_terminate ,您可以從那里打印來自第三方異常拋出的調用堆棧。 不幸的是,沒有辦法從終止處理程序中恢復,因此您的應用程序將會終止。

  2. 如果你調用std::set_unexpected ,那么你需要使用throw(MyControlledException)從你的函數中聲明盡可能多的,這樣當它們由於第三方調用的函數而拋出時,你的unexpected_handler將能夠給你一個細粒度的你的應用程序扔在哪里的想法。

我認為堆棧跟蹤應該讓你明白這一點。

在調試模式下編譯您的軟件並使用 valgrind 運行它。 它主要用於查找內存泄漏,但它也可以向您顯示發生異常的確切位置valgrind --leak-check=full /path/to/your/software

其他人已經建議使用宏和可能的自定義類。 但是如果你有一個異常層次結構,你還需要在拋出時指定異常類型:

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

THROW(Error, "An error occurred");

這里的假設是所有異常都接受一個字符串參數,這不是限制性的,因為可以將其他參數轉換為字符串(例如使用std::to_string() )並將它們連接成一個字符串。

除了按照 Frank Krueger 的建議,針對您自己的異常使用帶有宏的自定義類之外,您可能有興趣查看結構化異常處理機制(您是在 Windows 下編程,對嗎?)
檢查MSDN 上的結構化異常處理

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM