Как узнать точную строку кода, где было вызвано исключение? - PullRequest
33 голосов
/ 08 декабря 2008

Если я сгенерирую исключение самостоятельно, я могу включить в него любую информацию: номер строки кода и имя исходного файла. Примерно так:

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

Но что с необработанными исключениями или с исключениями, которые не были сгенерированы мной?

Ответы [ 9 ]

29 голосов
/ 08 декабря 2008

Лучшее решение - использовать пользовательский класс и макрос. : -)

#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;
    }
}
23 голосов
/ 08 декабря 2008

Кажется, что каждый пытается улучшить ваш код, чтобы исключить его, и никто не пытается ответить на фактически заданный вами вопрос.

Это потому, что это невозможно сделать. Если код, который вызывает исключение, представлен только в двоичном виде (например, в файле LIB или DLL), то номер строки пропал, и нет возможности подключить объект к строке в исходном коде.

20 голосов
/ 08 декабря 2008

Существует несколько возможностей узнать, где было сгенерировано исключение:

Использование макросов компилятора

Использование макросов __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 расширяется до однобайтовой строки. Это работает в отладке и выпуске. К сожалению, имена исходных файлов с исключениями для бросания кода помещаются в выходной исполняемый файл.

Stack Walking

Выясните местоположение исключения, пройдя стек вызовов.

  • В Linux с gcc функции backtrace () и backtrace_symbols () могут получать информацию о текущем стеке вызовов. См. gcc документацию , как их использовать. Код должен быть скомпилирован с -g, чтобы символы отладки помещались в исполняемый файл.

  • В Windows вы можете пройтись по стеку, используя библиотеку dbghelp и ее функцию StackWalk64. См. Статью Йохена Кальмбаха о CodeProject для получения более подробной информации. Это работает в отладке и выпуске, и вам нужно отправить файлы .pdb для всех модулей, о которых вы хотите получить информацию.

Вы даже можете объединить два решения, собирая информацию из стека вызовов при возникновении пользовательского исключения. Стек вызовов может храниться в исключении, как в .NET или Java. Обратите внимание, что сбор стека вызовов в Win32 очень медленный (мой последний тест показал около 6 собранных стеков вызовов в секунду). Если ваш код вызывает много исключений, этот подход значительно замедляет вашу программу.

3 голосов
/ 08 декабря 2008

Если у вас есть отладочная сборка и вы запускаете ее в отладчике Visual Studio, то вы можете взломать отладчик при возникновении любых исключений, прежде чем он распространится на весь мир.

Включите это с помощью альтернативы меню Отладка> Исключения , а затем отметьте типы исключений, которые вас интересуют.

Вы также можете добавить возможность создания файла дампа, если исходный код приложения принадлежит вам. С файлом дампа и файлами PDB (символами) для конкретной сборки вы получите, например, трассировки стека с WinDbg.

2 голосов
/ 08 декабря 2008

Самое простое решение - использовать макрос:

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

void f() {
    throw_line("Oh no!");
}
1 голос
/ 26 июля 2009

Я нашел 2 решения, но ни одно из них не является полностью удовлетворительным:

  1. Если вы позвоните по номеру std::set_terminate, вы сможете оттуда распечатать стек вызовов прямо из стороннего исключения. К сожалению, нет способа восстановиться из обработчика завершения, и, следовательно, ваше приложение умрет.

  2. Если вы вызываете std::set_unexpected, вам нужно объявить как можно больше из ваших функций с помощью throw(MyControlledException), чтобы при вызове функций из-за сторонних функций вы могли unexpected_handler дать вам детальное представление о том, куда бросило ваше приложение.

1 голос
/ 08 декабря 2008

Я думаю, что трассировка стека должна привести вас к сути.

0 голосов
/ 12 сентября 2018

Вдохновленный ответом Фрэнка Крюгера и документацией для std :: nested_exception , я понял, что вы можете объединить ответ Фрэнка, который я использовал некоторое время, с 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__) )

`` `

0 голосов
/ 08 декабря 2008

Помимо использования собственного класса с макросом, как предложено Фрэнком Крюгером, для собственных исключений, вам может быть интересно взглянуть на механизм структурированной обработки исключений (вы программируете под windows, верно?)
Проверьте Структурная обработка исключений на MSDN

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...