Как узнать точную строку кода, где было вызвано исключение?

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

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

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

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

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

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

Прохождение стека

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

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

Ответ 8

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

Ответ 9

Вдохновленный ответом Фрэнка Крюгера и документацией для 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 << "\[email protected] 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__) )

"""