Как узнать точную строку кода, где было вызвано исключение?
Если я сам создаю исключение, я могу включить в него любую информацию: номер строки кода и имя исходного файла. Что-то вроде этого:
throw std::exception("myFile.cpp:255");
Но что с необработанными исключениями или с исключениями, сгенерированными не мной?
9 ответов
Кажется, что все пытаются улучшить ваш код, чтобы исключить его, и никто не пытается ответить на фактически заданный вами вопрос.
Это потому, что это невозможно сделать. Если код, который вызывает исключение, представлен только в двоичном виде (например, в файле LIB или DLL), то номер строки пропал, и нет способа соединить объект с линией в исходном коде.
Лучшее решение - использовать пользовательский класс и макрос.:-)
#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;
}
}
Есть несколько возможностей узнать, где было сгенерировано исключение:
Использование макросов компилятора
С помощью __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 расширяется до однобайтовой строки. Это работает в отладке и выпуске. К сожалению, имена исходных файлов с исключениями для бросания кода помещаются в выходной исполняемый файл.
Ходьба стека
Выясните местоположение исключения, пройдя по стеку вызовов.
В Linux с gcc функции backtrace() и backtrace_symbols() могут получить информацию о текущем стеке вызовов. Смотрите документацию gcc как их использовать. Код должен быть скомпилирован с -g, чтобы символы отладки помещались в исполняемый файл.
В Windows вы можете пройтись по стеку, используя библиотеку dbghelp и ее функцию StackWalk64. Подробности смотрите в статье Йохена Калмбаха о 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.
Вдохновленный ответом Фрэнка Крюгера и документацией для 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__) )
`` `
Я нашел 2 решения, но ни одно из них не является полностью удовлетворительным:
Если вы позвоните
std::set_terminate
Вы можете оттуда распечатать стек вызовов прямо из стороннего исключения. К сожалению, нет способа восстановиться из обработчика завершения, и, следовательно, ваше приложение умрет.Если вы позвоните
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");
Здесь предполагается, что все исключения принимают единственный строковый аргумент.
Помимо использования собственного класса с макросом, как предложено Фрэнком Крюгером, для ваших собственных исключений, вам может быть интересно взглянуть на механизм структурированной обработки исключений (вы программируете под окнами, верно?)
Проверьте структурированную обработку исключений на MSDN