Code Monkey home page Code Monkey logo

ubbook's Introduction

Путеводитель C++ программиста по неопределенному поведению

Паникуй!


Коротко о том, зачем и почему

Все начинается просто и незатейливо: обычный десятиклассник увлекается программированием, знакомится с алгоритмическими задачками, решения которых должны быть быстрыми. Узнает о языке C++, учит минимальный синтаксис, основные конструкции, контейнеры, решает задачи с предопределенным и всегда корректным форматом ввода и вывода, и горя не знает...

В это же время, где-то в большом мире, матерые разработчики каждый день ругают то одни языки программирования, то другие. По самым разным причинам: не удобно, нет какой-то возможности, много лишних букв писать, ошибки в стандартной библиотеке... Но есть язык, который ругают за все и особенно за такую непонятную и таинственную вещь как неопределенное поведение (undefined behavior, UB).

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

А потом еще в течение нескольких лет он наткнется на самые кошмарные и невероятные ужасы, поджидающие программистов на C++ почти на каждом шагу. Так и появится эта серия заметок, собирающая наиболее отвратительные примеры, на которые очень легко наткнуться при решении повседневных задач.


«Преждевременная оптимизация — корень всех зол» (Д. Кнут или Э. Хоар — в зависимости от того, какой источник смотрите). Язык С++, пожалуй, наиболее яркая тому демонстрация: огромное количество ошибок в C++ программах связаны с неопределенным поведением, заложенным в фундаменте языка просто для того, чтобы дать простор оптимизациям на этапе компиляции.

Если вы собираетесь писать на C++ код, в работоспособности которого хотите быть хоть немного уверенными, стоит знать о существовании различных подводных камней и ловко расставленных мин в стандарте языка, его библиотеке, и всячески их избегать. Иначе ваши программы будут работать правильно только на конкретной машине и только по воле случая.

Важно: этот сборник не является учебным пособием по языку и рассчитан на тех, кто уже знаком с программированием, с C++, и понимает основные его конструкции.


Содержание

  1. Что такое UB и как оно проявляется
  2. Как искать UB?
  3. Сужающие преобразования
  4. Целые и вещественные числа
    1. Переполнение знаковых целых чисел
    2. Числа с плавающей точкой
    3. Integer promotion
    4. char и знаковое расширение
    5. Унарный минус для беззнаковых чисел
  5. Нарушения lifetime объектов
    1. Висячие ссылки — общие случаи
    2. Автовывод типов и висячие ссылки
    3. std::string_view
    4. Range-based for
    5. Cамоинициализация
    6. std::vector и инвалидация ссылок
    7. Висячие ссылки в лямбдах
    8. Создание кортежей
    9. Внезапная мутабельность
    10. Proxy-объекты и ссылки
    11. use-after-move
    12. lifetime extension
    13. C++20 direct initialization и ссылочные поля
    14. Тернарный оператор
  6. (Не)работающий синтаксис
    1. Most Vexing Parse
    2. Const
    3. std::move
    4. Потерянный return
    5. Эллипсис и функции с произвольным числом аргументов
    6. operator ,
    7. function-try-block
    8. Пустые структуры и типы нулевого размера
    9. (Не)явное приведение типов
    10. Многомерный operator[]
    11. Операторы сравнения в C++20
    12. Атрибут [[assume]]
    13. Конструкторы по умолчанию и = default
  7. Стандартная библиотека
    1. NULL-терминированные строки
    2. Конструирование std::shared_ptr
    3. потоки ввода/вывода
    4. std::aligned_storage
    5. функции стантарной библиотеки как параметры
    6. std::ranges::views
    7. operator[] ассоциативных контейнеров
    8. std::enable_if/std::void_t
    9. Конструкторы контейнеров
    10. std::uniform_int_distribution
    11. std::ranges::transform | filter
    12. vector::reserve и vector::resize
  8. Исполнение программы
    1. Бесконечные циклы
    2. Рекурсия
    3. Ложный noexcept
    4. Переполнение буфера
    5. Сборщик мусора
    6. RAII vs (N)RVO
    7. Разыменование nullptr
    8. Static initialization order fiasco
    9. Static inline
    10. ODR violation
    11. Зарезервированные имена
    12. Тривиальные типы и ABI
    13. Неинициализированные переменные
    14. Ranges. Unreachable sentinel
    15. Невиртуальные виртуальные функции
    16. Variable length array
    17. ODR violation и разделяемые библиотеки
  9. Происхождение указателей
    1. Невалидные указатели
    2. Placement operator new[]
    3. Невыровненные ссылки
  10. Параллелизм
    1. Race condition
    2. shared_ptr
    3. thread::join
    4. Повторный захват mutex
    5. Signal-unsafe
    6. condition_variable

Помощь

В тексте могут быть ошибки, опечатки, неточности, он может устаревать. Пожелания, предложения и замечания приветствуются: можно завести issue или сделать pull request.

И еще кое-что

Бегать за вами и судиться автор сего сборника не будет, но все-таки:

На этот проект можно ссылаться. Можно приводить примеры из него, со ссылками, конечно же.

Для копирования и иного воспроизведения надо получить согласие автора

Нельзя использовать в платных сервисах или взимать плату за обучение по этим материалам.

Ну и самое последнее примечание

Черновое название этой работы, "Ружье достаточной огневой мощи, чтобы на нем повеситься", как могли догадаться искушенные читатели, было эдаким реверансом в сторону известного (но очень плохо состарившегося) сборника по C++ "Веревка достаточной длины, чтобы выстрелить себе в ногу" от Алана Голуба. Но, к сожалению, мы живем в нежном мире победивших алгоритмов ранжирования и надзорных органов, то и дело стремящихся кого-нибудь от чего-нибудь защитить.

Автор, конечно, очень бы хотел защитить всех от C++, и именно этому и служит данных сборник, но с заблокированным и пессимизированным репозиторием прогресса в этом направлении не будет.

Copyright 2020-2024 Dmitry Sviridkin

ubbook's People

Contributors

alinaut avatar beeblerox avatar blepons avatar dimka-rs avatar dotcypress avatar fenolftalein avatar gavriliuk avatar gleb-kov avatar ldvsoft avatar molkree avatar mrfeod avatar nekrolm avatar profelis avatar sgshulman avatar sigasigasiga avatar tsayukov avatar vladimirgamalyan avatar vsevak avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ubbook's Issues

More fun with static initialization order fiasco and standard library

Even in C++11!

// a.cpp
#include <iostream>
void print_hello() {
    std::cout << "hello\n";
}
// b.cpp
void print_hello();
struct StaticObject {
    StaticObject() {
        print_hello();
    }
} static_object;
int main() {
}

If I compile these two locally with g++ -std=c++17 a.cpp b.cpp, the resulting a.exe crashes. My compiler version is g++ (Rev2, Built by MSYS2 project) 10.3.0 on Windows.
I am also able to reproduce this behavior by compiling g++ -std=c++17 b.cpp a.cpp with g++-10 (Ubuntu 10.3.0-1ubuntu1~20.04) 10.3.0

I believe it is because b.cpp does not include <iostream> and static_object gets constructed before anything in a.cpp, corresponding std::ios_base::Init provided by <iostream> in particular. Hence, when print_hello() is called, std::cout is not initialized yet, hence the crash.

противоречие в двух главах

в главе про const говорилось, что в цикле нельзя единожды вычислить size() константного объекта в цикле и переиспользовать, так как существует const_cast, который может изменять состояние объекта

     for (size_t i = 0; i < v.size(); ++i) { // значение v.size() нельзя
                                             // единожды сохранить в регистре

а в главе про race condition говорится, что компилятор таки может соптимизировать вычисление функции size()

    // Race condition запрещен, от модификации v в 
    // параллельном потоке нас «защищает» UB.
    
    // А значит можно соптимизировать вычисление size
    // const size_t v_size = v.size();
    // for (size_t i = 0; i < v_size; ++i) { ... }
    return sum;   

наверное, второй пример некорректен, и его следует заменить. или же тут всё не так однозначно?

Pointer arithmetics on a raw memory is UB

Apparently it's impossible to implement std::vector:
https://stackoverflow.com/questions/60481204/dynamic-arrays-in-c-without-undefined-behavior
https://www.youtube.com/watch?v=IAdLwUXRUvg&t=1267s

In particular:

Неинициализированные переменные в Go

В статье Неинициализированные переменные говорится, что в Go нельзя использовать неинициализированные переменные:

Новые современные языки программирования обычно запрещают использование неинициализированных переменных. И выдают ошибку компиляции, если такое происходит. Так сделано в Rust, Kotlin, Go. И еще в куче языков.

Однако Go такое не запрещает (выдержка из Go Specification, интерактивный пример - A Tour of Go):

When storage is allocated for a variable, either through a declaration or a call of new, or when a new value is created, either through a composite literal or a call of make, and no explicit initialization is provided, the variable or value is given a default value. Each element of such a variable or value is set to the zero value for its type: false for booleans, 0 for numeric types, "" for strings, and nil for pointers, functions, interfaces, slices, channels, and maps. This initialization is done recursively, so for instance each element of an array of structs will have its fields zeroed if no value is specified.

Cкорее даже поощряет - What is the zero value, and why is it useful?

Поддержка сборщика мусора: UB только при strict pointer safety?

Кажется, что UB от использования хитрых указателей возникает только если у реализации "strict pointer safety" — https://eel.is/c++draft/basic.stc.dynamic.safety#4

А так обычно у всех "relaxed pointer safety", это implementation-defined, никакого UB.

Можно даже в рантайме проверить: https://en.cppreference.com/w/cpp/memory/gc/get_pointer_safety

И даже под Valgrind, видимо, чисто теоретически подстроиться, если увидел preferred

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.