diff --git a/README.md b/README.md index e36022f..5cb4801 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ 5. [std::enable_if/std::void_t](syntax/enable_if_void_t.md) 6. [Потерянный return](syntax/missing_return.md) 7. [Эллипсис и функции с произвольным числом аргументов](syntax/c_variadic.md) - 8. [`operator[] ` ассоциативных котейнеров](syntax/map_subscript.md) + 8. [`operator[] ` ассоциативных контейнеров](syntax/map_subscript.md) 9. [потоки ввода/вывода](syntax/iostreams.md) 10. [`operator ,`](syntax/comma_operator.md) 11. [function-try-block](syntax/function-try-catch.md) diff --git a/concurrency/condition_variable.md b/concurrency/condition_variable.md index bd6b5ae..f044bcb 100644 --- a/concurrency/condition_variable.md +++ b/concurrency/condition_variable.md @@ -34,7 +34,7 @@ void task2() { event_happened = true; } // Обратите внимание: вызов notify не обязан быть под захваченной блокировкой. - // Однако, в ранних версиях msvc, а тажке в очень старой версии из boost были + // Однако, в ранних версиях msvc, а также в очень старой версии из boost были // баги, требующие удерживать мьютекс захваченным во время вызова notify() // Но есть случай, когда делать вызов notify под блокировкой необходимо — если // другой тред может вызвать, например, завершаясь, деструктор объекта cv diff --git a/concurrency/race_condition.md b/concurrency/race_condition.md index fbd8792..fea040a 100644 --- a/concurrency/race_condition.md +++ b/concurrency/race_condition.md @@ -9,7 +9,7 @@ Увы, так не работает. Правильность использования этих инструментов в C++ нужно контроллировать самостоятельно, пристально изучая каждую строчку кода. Мы все равно втыкаемся в проблемы синхронизации доступа, с аккуратным развешиванием мьютексов и атомарных переменных. -Ситуация (_race condition_), в которой один поток программы модифицирует объект, а другой, в то же самое время, читает значения из этого объекта — или просто два потока одновременно пытаются модифицировать один объект — совершенно ясно, является ошибочной. Результат чтения может выдать какой-то странный промежуточный объект. Совместная запись — породить какое-то мутировавшее премешаное значенее. Независимо от языка программирования. +Ситуация (_race condition_), в которой один поток программы модифицирует объект, а другой, в то же самое время, читает значения из этого объекта — или просто два потока одновременно пытаются модифицировать один объект — совершенно ясно, является ошибочной. Результат чтения может выдать какой-то странный промежуточный объект. Совместная запись — породить какое-то мутировавшее премешаное значение. Независимо от языка программирования. Но в C++ это не просто ошибка. Это неопределенное поведение. И «возможности» для оптимизации diff --git a/how_to_find_ub.md b/how_to_find_ub.md index 1ca2f48..b8d5692 100644 --- a/how_to_find_ub.md +++ b/how_to_find_ub.md @@ -17,7 +17,7 @@ ---- -Компиляторы `Clang` и `GCC` с влюченными флагами `-Wall -Wpedantic` [способны](https://godbolt.org/z/zM4r1s) находить некоторые ошибки. +Компиляторы `Clang` и `GCC` с включенными флагами `-Wall -Wpedantic` [способны](https://godbolt.org/z/zM4r1s) находить некоторые ошибки. ---- @@ -65,7 +65,7 @@ static_assert((div_test(A, B), true)); // Compliation error, zero division ---- Тесты, различные сборки, статический и динамический анализ — способы немного поднять уверенность в том, что в вашем коде нет UB. Дать же точную гарантию может только коллегия экспертов, которые будут сверять каждую строчку кода с буквой стандарта и трижды друг друга перепроверять. И даже этого может быть недостаточно. -Еще есть путь отключения каких-либо оптимизаций флагами компилятора, флаги включащие различные нарушения стандарта (знаменитый `-fpermissive`), превращающие язык C++ во что-то совершенно иное. Но призываю вас никогда не идти этим путем. Ваш код станет непереносимым. Ваш код перестанет быть кодом на C++. Лучше сразу возьмите другой язык программирования. +Еще есть путь отключения каких-либо оптимизаций флагами компилятора, флаги включающие различные нарушения стандарта (знаменитый `-fpermissive`), превращающие язык C++ во что-то совершенно иное. Но призываю вас никогда не идти этим путем. Ваш код станет непереносимым. Ваш код перестанет быть кодом на C++. Лучше сразу возьмите другой язык программирования. ### Полезные ссылки 1. https://gcc.gnu.org/onlinedocs/gcc/Warning-Options.html diff --git a/lifetime/for_loop.md b/lifetime/for_loop.md index d17b799..3d8eb17 100644 --- a/lifetime/for_loop.md +++ b/lifetime/for_loop.md @@ -31,7 +31,7 @@ const int& y = std::max([](const int& a, const int& b) -> const int& { // Со ссылками на 4, 5 успешно отрабатывает std::max — // их время жизни еще не закончилось. Ссылки валидны. -// Ссылка-результат присваевается `y`. Продлений жизни не происходит — +// Ссылка-результат присваивается `y`. Продлений жизни не происходит — // все временные объекты уже безуспешно попытали счастья на аргументах функций. // Дело доходит до ; Время жизни всех объектов 1,2,3,4,5 заканчивается. // `y` становится висячей. Занавес. @@ -118,7 +118,7 @@ auto&& container_ = MakeShape().vertexes; // Подобъект vertexes — считается таким же временным. // Его время жизни закончится на ; // Но он встречает && ссылку, область видимости которой простирается ниже -// и продлевает ему жизнь. Причем придлевается жизнь не только лишь подобъекту +// и продлевает ему жизнь. Причем продлевается жизнь не только лишь подобъекту // vertexes, а целиком временному объекту Shape, его содержащему ``` @@ -127,7 +127,7 @@ auto&& container_ = MakeShape().vertexes; auto&& container_ = MakeShape().Vertexes(); // временный объект Shape живет до ;. Но он встречает неявную const& // ссылку в методе Vertexes(). Ее область видимости ограничена телом метода. -// Продления жини не происходит. Возвращается ссылка на часть временного объекта +// Продления жизни не происходит. Возвращается ссылка на часть временного объекта // и присваивается ссылке `container_`. // Дело доходит до ;. Временный Shape умирает. // `container_` становится висячей ссылкой. Занавес. @@ -135,7 +135,7 @@ auto&& container_ = MakeShape().Vertexes(); Вот так все просто и сломано. -Кстати говоря: механизм продления жизни объекту с помощью ссылки на его подобъект — очень неочевидная штука. И, если, например, ваш код полагся на какие-то эффекты в деструкторах, можно получить не совсем то, [чего хотите](https://godbolt.org/z/9M946o). +Кстати говоря: механизм продления жизни объекту с помощью ссылки на его подобъект — очень неочевидная штука. И, если, например, ваш код полагается на какие-то эффекты в деструкторах, можно получить не совсем то, [чего хотите](https://godbolt.org/z/9M946o). --- @@ -152,7 +152,7 @@ for (auto cont = expr; auto x : cont) - Использовать `std::ranges::for_each` - Не использовать range-based for в C++, пока его не починят -## Полезыне ссылки +## Полезные ссылки 1. https://en.cppreference.com/w/cpp/algorithm/ranges/for_each 2. https://en.cppreference.com/w/cpp/language/range-for 3. https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary diff --git a/lifetime/self_init.md b/lifetime/self_init.md index 10d6907..785172d 100644 --- a/lifetime/self_init.md +++ b/lifetime/self_init.md @@ -64,7 +64,7 @@ ExtremelyLongClassName y { }() }; -ExtremelyLongСlassName z { +ExtremelyLongClassName z { [] ()-> decltype(z.Default()) { // Ok, well-defined // сложные вычисления return 1; diff --git a/lifetime/string_view.md b/lifetime/string_view.md index 5d706e4..fd8c0c8 100644 --- a/lifetime/string_view.md +++ b/lifetime/string_view.md @@ -28,7 +28,7 @@ int count_char(const char* s, char c) { } ``` -И будем либо дублировать код, немножно адапритуя его под C-строки, либо сделаем функцию +И будем либо дублировать код, немного адаптируя его под C-строки, либо сделаем функцию ```C++ int count_char_impl(const char* s, size_t len, char c) { @@ -97,7 +97,7 @@ int main() { } ``` -Сутация такая же, как с [ранее рассмотренным](use_after_free_in_general.md) `std::min`. +Ситуация такая же, как с [ранее рассмотренным](use_after_free_in_general.md) `std::min`. Только защититься от такой функции `common_prefix`, обернув ее в шаблон с помощью анализа rvalue/lvalue, намного сложнее: нам нужно разобрать случаи `const char*` и `std::string` для каждого аргумента — в общем, все то, от чего нас введение `std::string_view` «избавило». diff --git a/lifetime/use_after_free_in_general.md b/lifetime/use_after_free_in_general.md index 25332ff..0611205 100644 --- a/lifetime/use_after_free_in_general.md +++ b/lifetime/use_after_free_in_general.md @@ -4,7 +4,7 @@ Объект жил на стеке и умер. Или объект жил в куче и умер. Разница по сути не очень большая: обобщенный сценарий воспроизведения ошибки один и тот же — где-то остались указатель или ссылка на уже мертвый объект. А потом этой ссылкой (или указателем) воспользовались, чтобы обратиться к мертвому объекту. Такой спиритический сеанс заканчивается неопределенным поведением. Если повезет — будет ошибка сегментации с возможностью узнать, кто именно обратился. -Но все же между мервым объектом со стека или мервым объектом из кучи есть разница в возможности обнаружения методами динамического анализа: +Но все же между первым объектом со стека или первым объектом из кучи есть разница в возможности обнаружения методами динамического анализа: Для инструментации стека санитайзерами вообще говоря нужно перекомпилировать программу. Для инструментации кучи — можно подменить библиотеку с аллокатором. ------ diff --git a/lifetime/vector_invalidation.md b/lifetime/vector_invalidation.md index 2abab9e..2b99ce4 100644 --- a/lifetime/vector_invalidation.md +++ b/lifetime/vector_invalidation.md @@ -26,10 +26,10 @@ void run_actions(std::vector actions) { ``` Красиво, коротко, с неопределенным поведением и неправильно. -- `push_back` может вызвать реаллокацию вектора. Итераторы begin/end ивалидируются — цикл продолжится по уничтоженным данным. +- `push_back` может вызвать реаллокацию вектора. Итераторы begin/end инвалидируются — цикл продолжится по уничтоженным данным. - Если реаллокации не произойдет, цикл пройдет только по тому набору элементов, что были в векторе изначально. До добавленных в процессе дело не дойдет. -Корректый код: +Корректный код: ```C++ void run_actions(std::vector actions) { for (size_t idx = 0; idx < actions.size(); ++idx) { diff --git a/numeric/overflow.md b/numeric/overflow.md index 505e639..ab49bd0 100644 --- a/numeric/overflow.md +++ b/numeric/overflow.md @@ -31,7 +31,7 @@ if (x > 0 && a > 0 && x + a <= 0) { Но, увы, это неопределенное поведение. И компилятор имеет полное право [выкинуть](https://godbolt.org/z/dhs83T) такую проверку. -Искусственный пример может быть недостаточно убедительным, так что обратим внимание на следущую, вполне серьезную, функцию вычисления полиномиального хэша строки: +Искусственный пример может быть недостаточно убедительным, так что обратим внимание на следующую, вполне серьезную, функцию вычисления полиномиального хэша строки: ```C++ int hash_code(std::string s) { int h = 13; diff --git a/runtime/array_overrun.md b/runtime/array_overrun.md index 4362da8..3547dfc 100644 --- a/runtime/array_overrun.md +++ b/runtime/array_overrun.md @@ -12,7 +12,7 @@ И еще многие другие, преимущественно работающие со строками, функции. -Эти функции доставляли и продолжают доставлять проблемы. Некоторые компиляторы (msvc) по умолчанию откажутся собирать ваш код, если увидят одну из них. Другие будут менее заботливыми и, возможно, выдадут предупреждение. По крайней мере про функцию `gets` уж точно. Если с другими фунциями у программиста есть возможность уберечься (проверка до вызова; у `scanf` можно указать размер в ограничение строке), то с `gets` — без вариантов. +Эти функции доставляли и продолжают доставлять проблемы. Некоторые компиляторы (msvc) по умолчанию откажутся собирать ваш код, если увидят одну из них. Другие будут менее заботливыми и, возможно, выдадут предупреждение. По крайней мере про функцию `gets` уж точно. Если с другими функциями у программиста есть возможность уберечься (проверка до вызова; у `scanf` можно указать размер в ограничение строке), то с `gets` — без вариантов. Для большинства старых небезопасных сишных функций сейчас есть «безопасные» аналоги с размерами буферов. Часть из них не стандартизирована, часть стандартизирована. Все это породило огромное количество костылей с макроподстановками для работы со всем этим зоопарком. Но сейчас не об этом. diff --git a/runtime/endless_loop.md b/runtime/endless_loop.md index d71a752..a2c75c1 100644 --- a/runtime/endless_loop.md +++ b/runtime/endless_loop.md @@ -1,6 +1,6 @@ # Бесконечные циклы и проблема останова -Определить, завершается или не завершается программа на конкретном наборе данных — алгоритимически невозможно в общем случае. +Определить, завершается или не завершается программа на конкретном наборе данных — алгоритмически невозможно в общем случае. Но в стандартах C и C++ зачем-то сказано, что валидная программа должна либо гарантированно завершаться, либо гарантированно производить обозреваемые эффекты: запрашивать ввод-вывод, взаимодействовать с `volatile` переменными и подобное. А иначе поведение программы неопределенное. Так что «правильные» компиляторы C++ настолько суровы, что способны решать алгоритмически неразрешимые задачи. @@ -40,7 +40,7 @@ int main () { return 0; } ``` -Комилятор увидел, что единственный выход из цикла — `return 1`. У цикла нет никаких видимых эффектов. Так что компилятор просто заменил его на `return 1` +Компилятор увидел, что единственный выход из цикла — `return 1`. У цикла нет никаких видимых эффектов. Так что компилятор просто заменил его на `return 1` Если же попытаться узнать, что за тройку «нашла» программа — цикл вернется. diff --git a/runtime/garbage_collector.md b/runtime/garbage_collector.md index 9e6b43f..2cc1c01 100644 --- a/runtime/garbage_collector.md +++ b/runtime/garbage_collector.md @@ -99,7 +99,7 @@ private: } ``` -Эти функции в настоящеее время ничего не делают. Они нужны только для формального следования букве стандарта. +Эти функции в настоящее время ничего не делают. Они нужны только для формального следования букве стандарта. Если вы верите, что когда-нибудь в C++ появится сборщик мусора, будьте любезны пользоваться этими прекрасными функциями, чтобы ваша программа оставалась корректной и в далеком будущем. @@ -107,10 +107,10 @@ private: -------- -Надо понимать, что сам по себе сборщик мусора для C++ не является чем-то сверхъестественным. На C/C++ написаны, например, сборщики мусора для JVM. Никто не мещает задействовать их же в C++-программах: просто используем альтернативные функции для выделения памяти. С их помощью даже можно переопределить поведение операторов `new` и `delete`. Но очень мало какой код на C++ пишется в предположении, что под этими операторами работает сборщик мусора. +Надо понимать, что сам по себе сборщик мусора для C++ не является чем-то сверхъестественным. На C/C++ написаны, например, сборщики мусора для JVM. Никто не мешает задействовать их же в C++-программах: просто используем альтернативные функции для выделения памяти. С их помощью даже можно переопределить поведение операторов `new` и `delete`. Но очень мало какой код на C++ пишется в предположении, что под этими операторами работает сборщик мусора. Проверить, не запустили ли вашу программу в светлом мире со сборщиком мусора, можно вызвав функцию `get_pointer_safety`. Она возвращает одно из трех значений: -- `pointer_safety::strict` — играть с восставновлением указателей абы откуда просто так нельзя; сборщик мусора, возможно, работает. +- `pointer_safety::strict` — играть с восстановлением указателей абы откуда просто так нельзя; сборщик мусора, возможно, работает. - `pointer_safety::relaxed` — с указателями нет никаких проблем, выделенная память никуда сама по себе не денется. - `pointer_safety::preferred` — с указателями нет никаких проблем, выделенная память никуда сама по себе не денется, но, возможно, работает детектор утечек, которому важны пометки `declare_reachable`/`undeclare_reachable`. diff --git a/runtime/nullptr_dereference.md b/runtime/nullptr_dereference.md index ec55aa0..681b766 100644 --- a/runtime/nullptr_dereference.md +++ b/runtime/nullptr_dereference.md @@ -1,7 +1,7 @@ # Разыменование нулевых указателей. Самая крутая ошибка с самыми жуткими последствиями. `null` вообще называют [ошибкой на миллиард долларов](https://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare/). -От них страдает куча кода, на самых разных языках программирования. Но если в условной `Java` при обращении по `null`-ссылке вы получите исключение с вполне предсказуемыми последствиями (ну, упало и упало), то в великом и ужасном C++, а также в C за вами придет неопределенное поведение. И оно будет дествительно неопределенным! +От них страдает куча кода, на самых разных языках программирования. Но если в условной `Java` при обращении по `null`-ссылке вы получите исключение с вполне предсказуемыми последствиями (ну, упало и упало), то в великом и ужасном C++, а также в C за вами придет неопределенное поведение. И оно будет действительно неопределенным! Но для начала, конечно, надо отметить, что, после всех обсуждений туманных формулировок стандарта, в настоящее время есть некоторое [соглашение](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#315), что все-таки не сама по себе конструкция `*p`, где `p` — нулевой указатель, вызывает неопределенное поведение. А `lvalue-to-rvalue` преобразование. Ну или менее формально, кратко и не совсем правильно: пока нет чтения или записи значения по этому самому нулевому адресу — все нормально. @@ -133,7 +133,7 @@ int main(int argc, char **argv) { На одних и тех же входных данных (вернее, их отсутствии), этот код завершается с [разными результатами](https://godbolt.org/z/zc4xGz) в зависимости от компилятора и уровня оптимизаций. -Если вы еще недостаточно напуганы, то вот еще замечательная [история](https://devblogs.microsoft.com/oldnewthing/?p=97635) о том, как весело и задорно падала фунция вида +Если вы еще недостаточно напуганы, то вот еще замечательная [история](https://devblogs.microsoft.com/oldnewthing/?p=97635) о том, как весело и задорно падала функция вида ```C++ void refresh(int* frameCount) diff --git a/runtime/odr_violation.md b/runtime/odr_violation.md index 30cf53a..6503b8f 100644 --- a/runtime/odr_violation.md +++ b/runtime/odr_violation.md @@ -67,7 +67,7 @@ int fun() { // CE: redefinition Что же делать? -В мире чистого C с этим борятся комплексом методов: +В мире чистого C с этим борются комплексом методов: 1. Ручной имплементацией механизма пространств имен — каждой функции и структуре в проекте дописывают префиксом имя проекта. 2. Настраивают видимость символов: diff --git a/runtime/recursion.md b/runtime/recursion.md index 673b6a7..0b08e85 100644 --- a/runtime/recursion.md +++ b/runtime/recursion.md @@ -19,7 +19,7 @@ struct Node { }; ``` -Такая структура совешенно законна для определения дерева, [компилируется и работает](https://godbolt.org/z/evecMd). И может быть удобнее, чем вариант с умными указателями. +Такая структура совершенно законна для определения дерева, [компилируется и работает](https://godbolt.org/z/evecMd). И может быть удобнее, чем вариант с умными указателями. Нам не нужно никак вручную управлять ресурсами, вектор позаботится обо всем самостоятельно. Пользуемся «правилом нуля» и не пишем ни деструктор, ни конструктора копирования, ни оператора перемещения/копирования, ничего — красота! diff --git a/runtime/unreachable_sentinel.md b/runtime/unreachable_sentinel.md index 26e81d4..7c4dcf1 100644 --- a/runtime/unreachable_sentinel.md +++ b/runtime/unreachable_sentinel.md @@ -119,7 +119,7 @@ struct Numbers { ``` Правда, семантика `operator !=` стала странной. Да и нужно `end()` из чего-то конструировать. Если стейт нашего генератора будет более сложным, например, выделяющим что-то на куче, мы получим дополнительные накладные расходы. Не очень zero-cost. -Поэтому в C++17 `range-based-for` исправили. Теперь он может [работать](https://godbolt.org/z/7vYxc4K6q) с граничиными итераторами разных типов. +Поэтому в C++17 `range-based-for` исправили. Теперь он может [работать](https://godbolt.org/z/7vYxc4K6q) с граничными итераторами разных типов. Но STL-алгоритмы все также [не работают](https://godbolt.org/z/MddGYWMdq). ```C++ diff --git a/runtime/virtual_functions.md b/runtime/virtual_functions.md index 523342a..451b6f8 100644 --- a/runtime/virtual_functions.md +++ b/runtime/virtual_functions.md @@ -37,7 +37,7 @@ public: В конструкторах и деструкторах в C++ виртуальная диспетчеризация методов не работает (В других языках — например, в C# или Java — наоборот, что доставляет свои проблемы). Почему так? При конструировании часть объекта-наследника, используемая в переопределенном методе, может быть еще не создана: конструкторы вызываются в порядке от базового класса к производному. -При деструктурировании наоборот — часть обекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free. +При деструктурировании наоборот — часть объекта-наследника уже уничтожена, и если позволить динамический вызов, можно легко получить use-after-free. Радуйтесь! Это одно из немногих мест в C++, где вас защитили от неопределенного поведения со временами жизни! @@ -74,7 +74,7 @@ void Processor::start() { Компиляторы уже [не выдают](https://godbolt.org/z/66fG6cjnd) замечательного предупреждения и подвох заметить стало сложнее. А ведь мы повысили уровень индирекции всего на один! А что будет если код нашего базового класса окажется сложнее?.. Наследование имплементаций — источник многих проблем, прячущихся за невинным желанием переиспользовать код. -Вызов виртуальных функций класса в его конструкторах и деструкторах почти всегдя является ошибкой сейчас или в будущем. +Вызов виртуальных функций класса в его конструкторах и деструкторах почти всегда является ошибкой сейчас или в будущем. Если же это не ошибка и так и задумывалось, то стоит использовать явный статический вызов с указанием имени класса (name qualified call). ```C++ diff --git a/syntax/const_launder.md b/syntax/const_launder.md index 307e2d5..65e8c06 100644 --- a/syntax/const_launder.md +++ b/syntax/const_launder.md @@ -155,14 +155,14 @@ int count_if(const std::vector& v, predicate p) { Вот менее тривиальный пример `const`, никак не способствующего оптимизации: ```C++ void find_last_zero_pos(const std::vector& v, - const int* *poiner_to_last_zero) { - *poiner_to_last_zero = 0; + const int* *pointer_to_last_zero) { + *pointer_to_last_zero = 0; for (size_t i = 0; i < v.size(); ++i) { // мы опять не можем один раз // сохранить значение v.size() if (v[i] == 0) { // внутри вектора есть поля типа int* — begin, end // что если pointer_to_last_zero указывает на один из них?!? - *poiner_to_last_zero = (v.data() + i); + *pointer_to_last_zero = (v.data() + i); } // пересчитываем size! } @@ -257,8 +257,8 @@ std::cout << unit.back().id << ""; p->~Unit(); ``` -Но поддерживать указатель, взвращенный оператором `new`, не всегда возмножно. -Он занимает место, его надо хранить, что неэффективно при реализации `optional`: для `int32_t` будет нужно в три раза больше места на 64хбитной системе (4 байта на `storage` + 8 байт на указатель)! +Но поддерживать указатель, взвращенный оператором `new`, не всегда возможно. +Он занимает место, его надо хранить, что неэффективно при реализации `optional`: для `int32_t` будет нужно в три раза больше места на 64-битной системе (4 байта на `storage` + 8 байт на указатель)! Поэтому в стандартной библиотеке с C++17 есть функция «отмывания» невесть откуда взявшихся указателей — `std::launder`. diff --git a/syntax/enable_if_void_t.md b/syntax/enable_if_void_t.md index 7432016..687b54e 100644 --- a/syntax/enable_if_void_t.md +++ b/syntax/enable_if_void_t.md @@ -195,7 +195,7 @@ template using my_void_t = typename my_void::type; // ok ``` -А вообще лучше переходить на C++20 и не заниматься всей это ерундой. Там специально для всех этих страшных конструкций читаемый синтактический сахар придумали. Конечно, не без затаившихся граблей, но об этом в другой раз. +А вообще лучше переходить на C++20 и не заниматься всей это ерундой. Там специально для всех этих страшных конструкций читаемый синтаксический сахар придумали. Конечно, не без затаившихся граблей, но об этом в другой раз. ## Полезные ссылки 1. https://en.cppreference.com/w/cpp/language/function_template diff --git a/syntax/map_subscript.md b/syntax/map_subscript.md index e47a1c4..87ff014 100644 --- a/syntax/map_subscript.md +++ b/syntax/map_subscript.md @@ -21,7 +21,7 @@ for (Word c : text) { map.put(key, map.containsKey(key) ? map.get(key) + 1 : 1); // поиск трижды! map.put(key, map.getOrDefault(key, 0) + 1); // поиск дважды! ``` -Оно, конечно, может быть, отоптимизируется JIT-комплятором... Но мы в C++ любим гарантии. +Оно, конечно, может быть, отоптимизируется JIT-компилятором... Но мы в C++ любим гарантии. С другой стороны, этот вызов конструктора, если элемент не найден, может выйти боком: diff --git a/syntax/most_vexing_parse.md b/syntax/most_vexing_parse.md index 087e5a9..1e24b5b 100644 --- a/syntax/most_vexing_parse.md +++ b/syntax/most_vexing_parse.md @@ -65,7 +65,7 @@ struct Worker { int main() { // ЭТО НЕ ВЫЗОВ КОНСТРУКТОРА! - Worker w(Timer()); // предобъявление функции, которая возврщает Worker и принимает функцию, возвращающую Timer и не принимающую ничего! + Worker w(Timer()); // предобъявление функции, которая возвращает Worker и принимает функцию, возвращающую Timer и не принимающую ничего! std::cout << w; // имя функции неявно преобразуется к указателю, который неявно преобразуется к bool // будет выведено 1 (true) diff --git a/syntax/move.md b/syntax/move.md index 3278896..ad24ab1 100644 --- a/syntax/move.md +++ b/syntax/move.md @@ -69,7 +69,7 @@ void test_v2(){ Во-вторых, стандарт C++ не специфицирует состояние, в котором должен остаться объект, _из_ которого произвели перемещение. Оно должно быть валидным в смысле вызова деструктора. Но более ничего не требуется. Объект не обязан быть пустым после перемещения. Его поля не обязаны быть зануленными. Так у `std::thread` после перемещения нельзя вызывать ни один из методов. А `std::unique_ptr` гарантированно становится пустым (`nullptr`). -Чаще всего и проще всего натолкнуться на use-after-move можно при реализцации конструкторов, заполняющих поля переданными аргументами — достаточно дать одинаковые (или почти одинаковые) имена полям и аргументам. +Чаще всего и проще всего натолкнуться на use-after-move можно при реализации конструкторов, заполняющих поля переданными аргументами — достаточно дать одинаковые (или почти одинаковые) имена полям и аргументам. ```C++ struct Person { diff --git a/syntax/stl_constructors.md b/syntax/stl_constructors.md index 1016719..6ed942c 100644 --- a/syntax/stl_constructors.md +++ b/syntax/stl_constructors.md @@ -59,7 +59,7 @@ std::vector v1 {3, 2}; // v1 == {3, 2} std::vector v2 (3, 2); // v2 == {2,2,2} ``` -А еще у контейнеров есть конструктор, принимающий пару итераторов. И, казалось бы, с ними уж проблем-то не будет, но у нас есть указтели, которые также являются итераторами. А еще есть тип `bool`: +А еще у контейнеров есть конструктор, принимающий пару итераторов. И, казалось бы, с ними уж проблем-то не будет, но у нас есть указатели, которые также являются итераторами. А еще есть тип `bool`: ```C++ bool array[5] = {true, false, true, false, true}; @@ -69,7 +69,7 @@ std::cout << vector.size() << "\n"; [Будет выведено](https://gcc.godbolt.org/z/jobeh6) 2, а не 5. Потому что указатели неявно приводятся к `bool`! -Собственно, эти прекрасные примеры показывают, почему «универсальная» иниациализация не универсальная. +Собственно, эти прекрасные примеры показывают, почему «универсальная» инициализация не универсальная. Чтобы не множить хаос в своих проектах, нужно быть осторожнее с объявлениями перегруженных конструкторов для своих типов. Лучше ввести статическую функцию, чем создавать перегруженные конструкторы, неожиданно взаимодействующие с неявным приведением типов и списками инициализации. diff --git a/syntax/zero_size.md b/syntax/zero_size.md index 376e46b..bd3b545 100644 --- a/syntax/zero_size.md +++ b/syntax/zero_size.md @@ -125,7 +125,7 @@ struct Image { }; ``` -Поле `data` в структуре `Image` имеет [нулевой размер](https://godbolt.org/z/d3xfdj3Ke). Это FAM (flexible array member). Очень удобная штука, чтобы получать доступ к массиву статически не известной длины, размещенному сразу после некоторого заголовка в бинарном буфере. Длина массива обычно указавывается в самом заголовке. FAM может быть только последним полем в структуре. +Поле `data` в структуре `Image` имеет [нулевой размер](https://godbolt.org/z/d3xfdj3Ke). Это FAM (flexible array member). Очень удобная штука, чтобы получать доступ к массиву статически не известной длины, размещенному сразу после некоторого заголовка в бинарном буфере. Длина массива обычно указывается в самом заголовке. FAM может быть только последним полем в структуре. Стандарт C++ такие фичи не разрешает. Но ведь есть GCC с его нестандартными включенными по умолчанию расширениями. @@ -135,7 +135,7 @@ struct S { char data[]; }; ``` -Чему будет равень размер структуры `S`? +Чему будет равен размер структуры `S`? В стандартном C пустые структуры в принципе запрещены. И поведение программы с ними не определено. GCC определяет их размер нулевым при компиляции C программ. А при компиляции C++ — размер, как мы выяснили ранее, единичный. Дело пахнет страшными багами и ночными кошмарами при неосторожном проектировании C++ библиотек с сишным интерфейсом или использованием C-библиотек в C++! @@ -190,7 +190,7 @@ int add(int x, int y) { в плане генерируемого кода? -Краткий ответ: да. Есть разница. Зависит от конкретной иплементации. Стандарт не гарантирует оптимизацию пустых аргументов. +Краткий ответ: да. Есть разница. Зависит от конкретной имплементации. Стандарт не гарантирует оптимизацию пустых аргументов. От перемены позиций тегов может меняться бинарный интерфейс. Поиграться с наиболее заметными изменениями можно на примере [msvc](https://godbolt.org/z/E68ojMb8f). diff --git a/what_is_ub.md b/what_is_ub.md index ce8620f..2fa5a69 100644 --- a/what_is_ub.md +++ b/what_is_ub.md @@ -15,12 +15,12 @@ Важно, что это «поведение не определено» означает, что произойти может что угодно: форматирование диска, ошибка компиляции, исключение, а может и все будет хорошо. Никаких гарантий не дается. Отсюда и происходят веселые, неожиданне и очень печальные в production-коде последствия. -И, конечно же, именно C и C++ наболее печально известны своим неопределенным поведением. +И, конечно же, именно C и C++ наиболее печально известны своим неопределенным поведением. Однако надо понимать, что эта особенность присуща и другим языкам. Во многих языках можно найти какой-нибудь редкий особенный пример с неопределенным поведением. Но именно в C и C++ оно встречается при написании почти любой программы. Слишком много фич языка содержат пункты с неопределенным поведением. ---- -И так, по каким же признакам можно заподозрить UB в программе и насколько неопределенное поведение дествительно неопределенное? +И так, по каким же признакам можно заподозрить UB в программе и насколько неопределенное поведение действительно неопределенное? Когда-то давно UB в коде могло повлечь действительно что угодно. Например, `gcc 1.17` [начинал запускать игрушечки](https://feross.org/gcc-ownage/). @@ -29,9 +29,9 @@ 1. Для данной конкретной платформы и компилятора в документации сказано что именно произойдет, несмотря на страшные слова «_undefined behavior_» в стандарте. И все будет хорошо. Вы знаете что делаете. Никакой неопределенности. Все классно. 2. UB при работе с памятью чаще всего заканчиваются ошибкой сегментации и получением прекрасного сигнала SIGSEGV от операционной системы. Программа падает. 3. Программа работает и штатно завершается. Но дает разные или неадекватные результаты от запуска к запуску. Также результаты меняются от сборки к сборке при изменении опций компилятора или самого компилятора. Никаких генераторов случайных чисел вы не использовали. -4. Программа ведет себя неправильно, несмотря на то, что в коде наставлено огромное множество проверок, `assert`'ов, `try-catch` блоков, каждый из которых «подтвержает» что все корректно. В отладчике видно, что вычисления идут корректно, но совершенно внезапно все ломается. +4. Программа ведет себя неправильно, несмотря на то, что в коде наставлено огромное множество проверок, `assert`'ов, `try-catch` блоков, каждый из которых «подтверждает» что все корректно. В отладчике видно, что вычисления идут корректно, но совершенно внезапно все ломается. 5. Программа выполняет код, который в ней есть, но не вызывался. Отрабатывают ни разу не вызываемые функции. -6. Компилятор «без причины» и без падаения отказывается собирать код. Линковщик выдает «невозможные и бессмысленные» ошибки. +6. Компилятор «без причины» и без падения отказывается собирать код. Линковщик выдает «невозможные и бессмысленные» ошибки. 7. Проверки в коде перестают исполняться. Под отладчиком видно, что исполнение не заходит внутрь веток `if` или `catch`, хотя по значениям переменных заход должен быть выполнен. 8. Внезапный необоснованный вызов `std::terminate`. 9. Бесконечные циклы становятся конечными и наоборот. @@ -39,7 +39,7 @@ --- С неопределенным поведением часто путают два других понятия. -1. Еще одна страшная аббревиатура UB — неуточненное (_unspecified_) поведение. Стандарт не уточняет, что именно может произойти, но описывает варианты. Так, например, порядок вычисления аргуметов фунции — поведение неуточненное. +1. Еще одна страшная аббревиатура UB — неуточненное (_unspecified_) поведение. Стандарт не уточняет, что именно может произойти, но описывает варианты. Так, например, порядок вычисления аргументов функции — поведение неуточненное. 2. Поведение, определяемое реализацией (_implementation-defined_) — надо смотреть документацию для вашей платформы и вашего компилятора. Эта парочка намного лучше неопределенного, хотя и имеет с ним одну общую черту: программа, полагающаяся на любое из них, вообще говоря, непереносима.