Все начинается просто и незатейливо: обычный десятиклассник увлекается программированием, знакомится с алгоритмическими задачками, решения которых должны быть быстрыми. Узнает о языке C++, учит минимальный синтаксис, основные конструкции, контейнеры, решает задачи с предопределенным и всегда корректным форматом ввода и вывода, и горя не знает...
В это же время, где-то в большом мире, матерые разработчики каждый день ругают то одни языки программирования, то другие. По самым разным причинам: не удобно, нет какой-то возможности, много лишних букв писать, ошибки в стандартной библиотеке... Но есть язык, который ругают за все и особенно за такую непонятную и таинственную вещь как неопределенное поведение (undefined behavior, UB).
Спустя лет пять или шесть наш простой десятиклассник, горя не видавший в море оторванных от реальности программ, внезапно узнает, что тем самым горячо нелюбимым языком всегда был, остается и будет его C++.
А потом еще в течение нескольких лет он наткнется на самые кошмарные и невероятные ужасы, поджидающие программистов на C++ почти на каждом шагу. Так и появится эта серия заметок, собирающая наиболее отвратительные примеры, на которые очень легко наткнуться при решении повседневных задач.
«Преждевременная оптимизация — корень всех зол» (Д. Кнут или Э. Хоар — в зависимости от того, какой источник смотрите). Язык С++, пожалуй, наиболее яркая тому демонстрация: огромное количество ошибок в C++ программах связаны с неопределенным поведением, заложенным в фундаменте языка просто для того, чтобы дать простор оптимизациям на этапе компиляции.
Если вы собираетесь писать на C++ код, в работоспособности которого хотите быть хоть немного уверенными, стоит знать о существовании различных подводных камней и ловко расставленных мин в стандарте языка, его библиотеке, и всячески их избегать. Иначе ваши программы будут работать правильно только на конкретной машине и только по воле случая.
В этой книге я собрал множество самых разных примеров как в коде на C и C++ можно наткнуться на неопределенное, неожиданное и совершенно ошибочное поведение. И хотя основной фокус книги всё же на неопределенном поведении, в некоторых разделах описываются вещи вполе специфицированные, но довольно неочевидные.
Важно: этот сборник не является учебным пособием по языку и рассчитан на тех, кто уже знаком с программированием, с C++, и понимает основные его конструкции.
- Что такое UB и как оно проявляется
- Как искать UB?
- Сужающие преобразования
- Целые и вещественные числа
- Нарушения lifetime объектов
- Висячие ссылки — общие случаи
- Автовывод типов и висячие ссылки
- std::string_view
- Range-based for
- Cамоинициализация
- std::vector и инвалидация ссылок
- Висячие ссылки в лямбдах
- Создание кортежей
- Внезапная мутабельность
- Proxy-объекты и ссылки
- use-after-move
- lifetime extension
- C++20 direct initialization и ссылочные поля
- Тернарный оператор
- (Не)работающий синтаксис
- Most Vexing Parse
- Const
- std::move
- Потерянный return
- Эллипсис и функции с произвольным числом аргументов
operator ,
- function-try-block
- Пустые структуры и типы нулевого размера
- (Не)явное приведение типов
- Многомерный operator[]
- Операторы сравнения в C++20
- Атрибут [[assume]]
- Конструкторы по умолчанию и = default
- implicit bool
- Стандартная библиотека
- NULL-терминированные строки
- Конструирование std::shared_ptr
- shared_from_this
- потоки ввода/вывода
- std::aligned_storage
- функции стантарной библиотеки как параметры
- std::ranges::views
operator[]
ассоциативных контейнеров- std::enable_if/std::void_t
- Конструкторы контейнеров
- std::uniform_int_distribution
- std::ranges::transform | filter
- vector::reserve и vector::resize
- std::function
- Исполнение программы
- Бесконечные циклы
- Рекурсия
- Ложный noexcept
- Переполнение буфера
- Сборщик мусора
- RAII vs (N)RVO
- Разыменование nullptr
- Static initialization order fiasco
- Static inline
- ODR violation
- Зарезервированные имена
- Тривиальные типы и ABI
- Неинициализированные переменные
- Ranges. Unreachable sentinel
- Невиртуальные виртуальные функции
- Variable length array
- ODR violation и разделяемые библиотеки
- Происхождение указателей
- Асинхронность и параллелизм
В тексте могут быть ошибки, опечатки, неточности, он может устаревать. Пожелания, предложения и замечания приветствуются: можно завести issue
или сделать pull request
.
Бегать за вами и судиться автор сего сборника не будет, но все-таки:
На этот проект можно ссылаться. Можно приводить примеры из него, со ссылками, конечно же.
Для копирования и иного воспроизведения надо получить согласие автора
Нельзя использовать в платных сервисах или взимать плату за обучение по этим материалам.
Черновое название этой работы, "Ружье достаточной огневой мощи, чтобы на нем повеситься", как могли догадаться искушенные читатели, было эдаким реверансом в сторону известного (но очень плохо состарившегося) сборника по C++ "Веревка достаточной длины, чтобы выстрелить себе в ногу" от Алана Голуба. Но, к сожалению, мы живем в нежном мире победивших алгоритмов ранжирования и надзорных органов, то и дело стремящихся кого-нибудь от чего-нибудь защитить.
Автор, конечно, очень бы хотел защитить всех от C++, и именно этому и служит данных сборник, но с заблокированным и пессимизированным репозиторием прогресса в этом направлении не будет.
Copyright 2020-2024 Dmitry Sviridkin