Какие способы поиска ошибок в C++ коде ты используешь?

«Какие способы поиска ошибок в C++ коде ты используешь?» — вопрос из категории Тестирование, который задают на 25% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В своей работе с C++ я применяю многоуровневый подход к поиску ошибок:

  1. Статический анализ кода: Использую инструменты на этапе компиляции и в CI/CD.

    • Компиляторы: Включаю все предупреждения (-Wall -Wextra -Wpedantic в GCC/Clang, /W4 в MSVC) и трактую их как ошибки (-Werror).
    • Clang-Tidy: Запускаю для проверки стиля, потенциальных багов и современных идиом C++ (clang-tidy -checks='*' ...).
    • Cppcheck: Для поиска неопределённого поведения, утечек памяти и логических ошибок.
  2. Динамический анализ и инструменты санитайзеров: Запускаю юнит-тесты и интеграционные тесты под этими инструментами.

    • AddressSanitizer (ASan): Обнаруживает ошибки работы с памятью (разыменование nullptr, выход за границы, use-after-free).
      clang++ -fsanitize=address -g program.cpp
      ./a.out
    • UndefinedBehaviorSanitizer (UBSan): Ловит неопределённое поведение (переполнение знаковых целых, строгие нарушения псевдонимов).
    • Valgrind (Memcheck): Классический инструмент для поиска утечек памяти и ошибок в работе с ней, особенно полезен, если санитайзеры недоступны.
  3. Отладка (Debugging): При возникновении сложных ошибок логически использую отладчик.

    • GDB/LLDB: Для пошагового выполнения, установки точек останова, проверки стека вызовов и значений переменных.
    • Интегрированные отладки в IDE (Visual Studio, CLion, Qt Creator): Они предоставляют удобный графический интерфейс для тех же операций.
  4. Логирование и трассировка: Встраиваю детальное логирование (например, с помощью spdlog) в критические участки кода, чтобы отслеживать состояние программы в реальном времени в production-like средах.

  5. Юнит-тестирование: Пишу тесты с использованием фреймворков (Google Test, Catch2). Это помогает не только находить регрессии, но и часто выявляет ошибки на этапе проектирования интерфейсов.

Комбинация этих методов позволяет отлавливать ошибки на самых ранних стадиях, от синтаксических до сложных runtime-проблем.