Skip to content

Latest commit

 

History

History
76 lines (53 loc) · 6.32 KB

how_to_find_ub.md

File metadata and controls

76 lines (53 loc) · 6.32 KB

Как искать неопределенное поведение?

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

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


Можно проверить код до компиляции различными статическими анализаторами:

  • CppCheck
  • Clang Static Analyzer
  • PVS Studio
  • И другие

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


Компиляторы Clang и GCC с включенными флагами -Wall -Wpedantic способны находить некоторые ошибки.


Мы можем сами проверять часть кода в compile-time на различных наборах входных данных, используя constexpr. В контексте, вычисляемом на этапе компиляции, UB запрещено:

constexpr int my_div(int a, int b) {
    return a / b;
}

namespace test {
template <unsigned int N>
constexpr int div_test(const int (&A)[N], const int (&B)[N]) {
    int x = 0;
    for (auto i = 0u; i < N; ++i) {
        x = ::my_div(A[i], B[i]);
    }
    return x;
}

constexpr int A[] = {1,2,3,4,5};
constexpr int B[] = {1,2,3,4,0};
static_assert((div_test(A, A), true)); // OK
static_assert((div_test(A, B), true)); // Compliation error, zero division
}

Но constexpr не везде применим, в зависимости от версии стандарта налагает ограничения на тело функции, а также неявно применяет inline спецификатор, «запрещая» отрывать определение функции в отдельную единицу трансляции (или, по-простому, определение придется разместить в заголовочном файле).


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

При сборке компиляторами Clang или GCC можно включить санитайзеры -fsanitize=undefined, -fsanitize=address, -fsanitize=thread, позволяющие отлавливать ошибки в run-time, но ценой значительных накладных расходов, так что пользоваться этими средствами нужно только на этапе тестирования и разработки.

Также для отладочных сборок код стандартных библиотек иногда инструментирован assert'aми. Так, например, сделано для различных итераторов стандартной библиотеке в поставке msvc (Visual Studio)


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


Тесты, различные сборки, статический и динамический анализ — способы немного поднять уверенность в том, что в вашем коде нет UB. Дать же точную гарантию может только коллегия экспертов, которые будут сверять каждую строчку кода с буквой стандарта и трижды друг друга перепроверять. И даже этого может быть недостаточно.

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

Полезные ссылки

  1. https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html
  2. https://clang.llvm.org/docs/AddressSanitizer.html
  3. https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
  4. https://shafik.github.io/c++/undefined_behavior/2019/05/11/explporing_undefined_behavior_using_constexpr.html
  5. https://docs.microsoft.com/en-us/cpp/build/reference/permissive-standards-conformance?view=msvc-160
  6. http://cppcheck.sourceforge.net/