Стек вызовов для исключений в C++

сегодня, в моем мультиплатформенном коде C++, у меня есть try-catch вокруг каждой функции. В каждом блоке catch я добавляю имя текущей функции к исключению и бросаю его снова, так что в самом верхнем блоке catch (где я, наконец, печатаю детали исключения) у меня есть полный стек вызовов, который помогает мне отслеживать причину исключения.

Это хорошая практика, или есть лучшие способы, чтобы получить стек вызовов для исключения?

9 ответов


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

сказав это, если вы действительно должны иметь трассировку стека, то нужно создать информацию стека вызовов один раз на сайте исключения. Нет единого портативного способа сделать это, но используя что-то вроде http://stacktrace.sourceforge.net/ в сочетании с аналогичной библиотекой для VC++ не должно быть слишком сложно.


то, что вы делаете, не является хорошей практикой. Вот почему:

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

2. Это громоздко.
Это то, что вы должны помнить, чтобы добавить к каждой функции. Если вы случайно пропустите функцию, это может привести к большому количеству путаница, особенно если это была функция, вызвавшая исключение. И любой, кто смотрит на ваш код, должен понять, что вы делаете. Кроме того, я уверен, что вы использовали что-то вроде __FUNC__ или __FUNCTION__ или __PRETTY_FUNCTION__, что, к сожалению, все нестандартно (в C++ нет стандартного способа получить имя функции).

3. Это медленно.
Распространение исключений в C++ уже довольно медленное, и добавление этой логики только сделает путь к коду замедлившийся. Это не проблема, если вы используете макросы для catch и rethrow, где вы можете легко удалить catch и rethrow в версиях выпуска вашего кода. В противном случае производительность может быть проблема.

хорошая практика
Хотя, возможно, не рекомендуется перехватывать и перестраивать каждую функцию для создания трассировки стека, рекомендуется присоединить имя файла, номер строки и имя функции, в которых первоначально было создано исключение. Если вы используете boost:: исключение с BOOST_THROW_EXCEPTION, вы получите это поведение бесплатно. Также полезно приложить пояснительную информацию к вашему исключению, которая поможет в отладке и обработке исключения. Тем не менее, все это должно произойти во время создания исключения; как только оно будет построено, ему должно быть разрешено распространяться на его обработчик... вы не должны неоднократно ловить и переосмысливать больше, чем это необходимо. Если вам нужно поймать и перестроить в определенной функции, чтобы прикрепите некоторую важную информацию, это нормально, но ловить все исключения в каждой функции и для целей прикрепления уже доступной информации просто слишком много.


одним из решений, которое может быть более изящным, является создание макроса/класса трассировщика. Поэтому в верхней части каждой функции вы пишете что-то вроде:

TRACE()

и макрос выглядит примерно так:

Tracer t(__FUNCTION__);

и трассировщик классов добавляет имя функции в глобальный стек при построении и удаляет себя после уничтожения. Тогда этот стек всегда доступен для ведения журнала или отладки, обслуживание намного проще (одна строка), и он не вызывает исключения накладные расходы.

примеры реализации включают такие вещи, как http://www.drdobbs.com/184405270, http://www.codeproject.com/KB/cpp/cmtrace.aspx и http://www.codeguru.com/cpp/v-s/debug/tracing/article.php/c4429. Также Linux функции такой http://www.linuxjournal.com/article/6391 можете сделать это изначально, как рассказывает этот переполнение стека вопрос: как создать stacktrace, когда мой gcc c++ приложение. На ACE_Stack_Trace эйса тоже стоит посмотреть.

независимо от того, метод обработки исключений является грубым, негибким и вычислительно дорогим. Решения Class-construction / macro намного быстрее и могут быть скомпилированы для сборки выпуска, если это необходимо.


ответ на все ваши проблемы-хороший отладчик, обычно http://www.gnu.org/software/gdb/ в linux или Visual Studio в Windows. Они могут дать вам трассировки стека по требованию в любой момент программы.

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


смотреть на это и. Это может быть близко к тому, что вы ищете. Это не кросс-платформенный, но ответ дает решения для gcc и Visual Studio.


исключение, которое не обрабатывается, остается для обработки вызывающей функции. Это продолжается до тех пор, пока исключение не будет обработано. Это происходит с или без try / catch вокруг вызова функции. Другими словами, если вызывается функция, которая не находится в блоке try, исключение, которое происходит в этой функции, автоматически передается в стек вызовов. Итак, все, что вам нужно сделать, это поместить самую верхнюю функцию в блок try и обработать исключение "..."в блоке catch. Это исключение будет поймать все исключения. Таким образом, ваша функция top-most будет выглядеть примерно так:

int main()
{
  try
  {
    top_most_func()
  }
  catch(...)
  {
    // handle all exceptions here
  }
}

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


есть хороший маленький проект, который дает довольно трассировку стека:

https://github.com/bombela/backward-cpp


еще один проект для поддержки трассировки стека:ex_diag. Нет макросов, кросс-платформенный присутствует, нет огромных потребностей в коде, инструмент быстрый, понятный и простой в использовании.

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


связывание с библиотекой libcsdbg (см.https://stackoverflow.com/a/18959030/364818 для исходного ответа) выглядит как самый чистый способ получения трассировки стека без изменения исходного кода или стороннего исходного кода (т. е. STL).

Это использует компилятор для инструментирования фактической коллекции стека, которая действительно хочет, чтобы вы хотели сделать.

Я не использовал его, и он испорчен GPL, но он выглядит как правильная идея.