Набрёл на путеводитель по неопределённому поведению (Nekrolm/ubbok). Оставлю удовольствие прочесть вам это самостоятельно, а я расскажу об интересных вещах, моментах которые я не знал/не осознавал.
1. Одним из способов проверки уб в вашем коде может быть тестирование функций/методов на компиляции с помощью constexpr (т.к. в constexpr контексте неопределённое поведение не компилируется).
2. Integer promotion.
В С++ арифметические операции определены не для всех числовых типов. Например, для всех типов меньше int’а их нет. В таких случаях перед выполнением операций над любым указанным типом (даже беззнаковым!!) происходит приведение к int (знаковому!):
unsigned short x = 0xFFFF;
unsigned short y = 0xFFFF;
auto z = x * y;Здесь будет переполнение int, которое является уб. Стало страшно.
4. std::min объявлен как (одна из версий)
template< class T >
constexpr const T& min( const T& a, const T& b );Из-за того, что тут возвращается ссылка, можно легко получить висячую ссылку:
const auto& x = std::min(1, 2);Если вам это критично, можно использовать версию с
std::initializer_list. Она возвращает по значению:template< class T >
constexpr T min( std::initializer_list<T> ilist );5.
std::make_tuple применяет грубо говоря std::decay_t ко всем аргументам. Это вроде факт известный. Но интересно, что случай, когда аргументом является std::reference_wrapper обрабатывается отдельно, из-за чего тип такого аргумента станет T&.И вот так делать не стоит:
std::tie(x, y) = std::tie(y, x);Это unspecified.
6. Посмотрим на такой код:
std::vector<bool> v;
v.push_back(false);
std::cout << v[0] << " ";
const auto b = v[0];
auto c = b;
c = true;
std::cout << c << " " << b;Как думаете, что он выведет?
Неправильно. Результатом будет 0 1 1.
Я давно знал, что
std::vector<bool> таит в себе опасности, но чтобы такие… 7. В С++ есть правило: если что-то может быть засчитано за объявление функции, оно будет за него засчитано. И тут начинается разного рода флекс.
Когда-то в начале второго курса я хотел инициализировать поля класса с помощью вызова конструктора круглыми скобками, но программа упорно не компилировалась. Тогда я понял проблему лишь спустя несколько часов после сдачи контрольной работы. Будьте внимательными.
8. Интересный пример:
int x = 5;
auto x_ptr = &x; // валидный указатель, его МОЖНО разыменовывать
auto x_end_ptr = (&x) + 1; // валидный указатель, но его НЕЛЬЗЯ разыменовывать
auto x_invalid_ptr = (&x) + 2; // невалидный указатель, само его существование недопустимо.К любому адресу можно добавить как минимум 1, т.к. любой объект грубо говоря считается массивом из одного элемента, и вы таким образом можете получить
end() этого массива (только не разыменовывайте). Двигаться дальше -- уб. Этот код содержит уб:
std::string str = "hell";
str.erase(str.begin() + 4 + 1 - 3);Несмотря на то, что указатель
str.begin() + 4 + 1 - 3 является валидным указателем, на моменте прибавления единицы мы получаем невалидный указатель, лежащий за end() строки -> ub. 9. gcc 1.7 в некоторых случаях уб (которое получалось задетектить) пытался запускать одну из нескольких игр.
Большинство разделов довольно известные кмк, но знание о таких моментах давались мне долгими вечерами на протяжении лет из десятков разных источников. Тут же всё структурированно, много примеров и понятных пояснений. Обязательно найдите время почитать.
===================
Если у вас есть свободное время, можете поправить опечатки/ошибки. Или может даже вкинуть свой необычный пример неопределённого поведения. Так сказать стать open source community member.
Как правильно заметил автор, 80% проблем с уб от висячих ссылок. Я когда-то смотрел лекции Ильи Мещерина по плюсам, где он сказал, что несколько раз в крупных проектах дебагал висячие ссылки. Тогда я посмеялся и подумал, как такое можно вообще допустить, а буквально через месяц 8 часов провёл за ровно тем же занятием на работе. Больше не смеюсь.