mirror of
https://github.com/Nekrolm/ubbook.git
synced 2025-12-18 13:14:41 +03:00
Fix a number of typos (#128)
Co-authored-by: Sergey Fukanchik <s.fukanchik@postgrespro.ru>
This commit is contained in:
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
Если вы собираетесь писать на C++ код, в работоспособности которого хотите быть хоть немного уверенными, стоит знать о существовании различных подводных камней и ловко расставленных мин в стандарте языка, его библиотеке, и всячески их избегать. Иначе ваши программы будут работать правильно только на конкретной машине и только по воле случая.
|
Если вы собираетесь писать на C++ код, в работоспособности которого хотите быть хоть немного уверенными, стоит знать о существовании различных подводных камней и ловко расставленных мин в стандарте языка, его библиотеке, и всячески их избегать. Иначе ваши программы будут работать правильно только на конкретной машине и только по воле случая.
|
||||||
|
|
||||||
В этой книге я собрал множество самых разных примеров как в коде на C и C++ можно наткнуться на неопределенное, неожиданное и совершенно ошибочное поведение. И хотя основной фокус книги всё же на неопределенном поведении, в некоторых разделах описываются вещи вполе специфицированные, но довольно неочевидные.
|
В этой книге я собрал множество самых разных примеров как в коде на C и C++ можно наткнуться на неопределенное, неожиданное и совершенно ошибочное поведение. И хотя основной фокус книги всё же на неопределенном поведении, в некоторых разделах описываются вещи вполне специфицированные, но довольно неочевидные.
|
||||||
|
|
||||||
**Важно:** этот сборник **не является учебным пособием** по языку и рассчитан на тех, кто уже знаком с программированием, с C++, и понимает основные его конструкции.
|
**Важно:** этот сборник **не является учебным пособием** по языку и рассчитан на тех, кто уже знаком с программированием, с C++, и понимает основные его конструкции.
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
И почти всегда это нежелательная, ошибочная ситуация, последствия которой варьируются от просто неправильного результата до чудовищных уязвимостей в безопасности.
|
И почти всегда это нежелательная, ошибочная ситуация, последствия которой варьируются от просто неправильного результата до чудовищных уязвимостей в безопасности.
|
||||||
|
|
||||||
Иногда, конечно, система может быть толерантна к таким ошибкам и серьезных проблем не возникнет: например, если страничка магазина запрашивает список товаров на складе, а в этот момент база данных склада обновляется, то вы, как пользователь, возможно, увидите неполный или устаревший список товаров или смесь из старых и новых данных. Но критического ничего не произойдет, если разработчики предусмотрели дополнительные проверки с запросом к базе данных в момент взаимодейтсвия с конкретным товаром из списка... Чтобы вы не купили то, чего больше не существует.
|
Иногда, конечно, система может быть толерантна к таким ошибкам и серьезных проблем не возникнет: например, если страничка магазина запрашивает список товаров на складе, а в этот момент база данных склада обновляется, то вы, как пользователь, возможно, увидите неполный или устаревший список товаров или смесь из старых и новых данных. Но критического ничего не произойдет, если разработчики предусмотрели дополнительные проверки с запросом к базе данных в момент взаимодействия с конкретным товаром из списка... Чтобы вы не купили то, чего больше не существует.
|
||||||
|
|
||||||
Файловая система — один из таких ресурсов, гонки за которым естественны и должны учитываться при разработке приложений. Да, самые низкоуровневые проблемы синхронизации чтения и записи в файловую систему берет на себя операционная система и (или) конкретный драйвер. Можно «спокойно» одновременно из разных процессов читать и писать в один и тот же файл и получать мусор или штатные ошибки: низкоуровневые операции `read` и `write` будут как-то упорядочены планировщиком ввода-вывода. С самой файловой системой при этом всё будет в порядке.
|
Файловая система — один из таких ресурсов, гонки за которым естественны и должны учитываться при разработке приложений. Да, самые низкоуровневые проблемы синхронизации чтения и записи в файловую систему берет на себя операционная система и (или) конкретный драйвер. Можно «спокойно» одновременно из разных процессов читать и писать в один и тот же файл и получать мусор или штатные ошибки: низкоуровневые операции `read` и `write` будут как-то упорядочены планировщиком ввода-вывода. С самой файловой системой при этом всё будет в порядке.
|
||||||
|
|
||||||
@@ -33,7 +33,7 @@ int main() {
|
|||||||
|
|
||||||
Это класcическая **TOCTOU** (*Time-of-Check-Time-of-Use*) ошибка. Между проверкой и открытием файла, файл может быть удален.
|
Это класcическая **TOCTOU** (*Time-of-Check-Time-of-Use*) ошибка. Между проверкой и открытием файла, файл может быть удален.
|
||||||
|
|
||||||
Но причем тут С++, если такая проблема существет для любого языка программирования?
|
Но причем тут С++, если такая проблема существует для любого языка программирования?
|
||||||
|
|
||||||
Действительно. Например, во всех версиях стандартной библиотеки Rust с 1.0 до 1.58.1 [была](https://blog.rust-lang.org/2022/01/20/cve-2022-21658.html) похожая ошибка в реализации функции `remove_dir_all`.
|
Действительно. Например, во всех версиях стандартной библиотеки Rust с 1.0 до 1.58.1 [была](https://blog.rust-lang.org/2022/01/20/cve-2022-21658.html) похожая ошибка в реализации функции `remove_dir_all`.
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
Так почему бы не взять какую-нибудь готовую серьезную библиотеку (`boost`, `abseil`) — там наверняка умные люди уже пострадали многие часы, чтобы предоставить удобные и безопасные инструменты — и забот не знать?!
|
Так почему бы не взять какую-нибудь готовую серьезную библиотеку (`boost`, `abseil`) — там наверняка умные люди уже пострадали многие часы, чтобы предоставить удобные и безопасные инструменты — и забот не знать?!
|
||||||
|
|
||||||
Увы, так не работает. Правильность использования этих инструментов в C++ нужно контроллировать самостоятельно, пристально изучая каждую строчку кода.
|
Увы, так не работает. Правильность использования этих инструментов в C++ нужно контролировать самостоятельно, пристально изучая каждую строчку кода.
|
||||||
Мы все равно втыкаемся в проблемы синхронизации доступа, с аккуратным развешиванием мьютексов и атомарных переменных.
|
Мы все равно втыкаемся в проблемы синхронизации доступа, с аккуратным развешиванием мьютексов и атомарных переменных.
|
||||||
|
|
||||||
Ситуация (_data race_), в которой один поток программы модифицирует объект, а другой, в то же самое время, читает значения из этого объекта — или просто два потока одновременно пытаются модифицировать один объект — совершенно ясно, является ошибочной. Результат чтения может выдать какой-то странный промежуточный объект. Совместная запись — породить какое-то мутировавшее премешаное значение. Независимо от языка программирования.
|
Ситуация (_data race_), в которой один поток программы модифицирует объект, а другой, в то же самое время, читает значения из этого объекта — или просто два потока одновременно пытаются модифицировать один объект — совершенно ясно, является ошибочной. Результат чтения может выдать какой-то странный промежуточный объект. Совместная запись — породить какое-то мутировавшее перемешанное значение. Независимо от языка программирования.
|
||||||
|
|
||||||
Но в C++ это не просто ошибка. Это неопределенное поведение. И «возможности» для оптимизации
|
Но в C++ это не просто ошибка. Это неопределенное поведение. И «возможности» для оптимизации
|
||||||
|
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ elapsed: 100
|
|||||||
- Переданная функция каким-то образом начнет исполнятся асинхронно (в фоновом потоке или в пуле потоком — это тоже не специфировано). И такое поведение по умолчанию во всех современных версиях GCC, Clang и MSVC.
|
- Переданная функция каким-то образом начнет исполнятся асинхронно (в фоновом потоке или в пуле потоком — это тоже не специфировано). И такое поведение по умолчанию во всех современных версиях GCC, Clang и MSVC.
|
||||||
- Переданная функция не будет выполнятся до тех пор, пока вы не вызовете `wait` или `get` у возвращенной `std::future`. И такое поведение долгое время было со старыми версиями компиляторов. Например, [GCC 5.4](https://gcc.godbolt.org/z/nY6Kv4Gdz)
|
- Переданная функция не будет выполнятся до тех пор, пока вы не вызовете `wait` или `get` у возвращенной `std::future`. И такое поведение долгое время было со старыми версиями компиляторов. Например, [GCC 5.4](https://gcc.godbolt.org/z/nY6Kv4Gdz)
|
||||||
|
|
||||||
Какое именно поведение вы хотите можно и нужно контроллировать с помощью вызова перегрузки `std::async` с дополнительным первым параметром типа `std::launch`:
|
Какое именно поведение вы хотите можно и нужно контролировать с помощью вызова перегрузки `std::async` с дополнительным первым параметром типа `std::launch`:
|
||||||
- `std::launch::async` — если вы действительно хотите асинхронное исполнение
|
- `std::launch::async` — если вы действительно хотите асинхронное исполнение
|
||||||
- `std::launch::deferred` — если нужно отложить до точки вызова `wait` или `get`
|
- `std::launch::deferred` — если нужно отложить до точки вызова `wait` или `get`
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ awaitable<void> handle_request(const Request& r)
|
|||||||
Корутины — очень сложные объекты, которые обманчиво просты в использовании из-за синтаксического сахара. В этом же весь смысл! Поддержка `async/await` на уровне языка и компиляторов делает простым то, что всегда было делать сложно вручную... Так в происходит в высокоуровневых и безопасных языках с автоматическим управлением памятью: Python, C#, JavaScript, Kotlin.
|
Корутины — очень сложные объекты, которые обманчиво просты в использовании из-за синтаксического сахара. В этом же весь смысл! Поддержка `async/await` на уровне языка и компиляторов делает простым то, что всегда было делать сложно вручную... Так в происходит в высокоуровневых и безопасных языках с автоматическим управлением памятью: Python, C#, JavaScript, Kotlin.
|
||||||
Но не в C++. И не в Rust. (И не в Zig).
|
Но не в C++. И не в Rust. (И не в Zig).
|
||||||
|
|
||||||
В примере выше есть как минимуи **три** точки отказа, содержащих ошибки. Можете подумать об этом, пока мы будем разворачивать проблемы корутин С++.
|
В примере выше есть как минимум **три** точки отказа, содержащих ошибки. Можете подумать об этом, пока мы будем разворачивать проблемы корутин С++.
|
||||||
|
|
||||||
### Что такое корутина?
|
### Что такое корутина?
|
||||||
|
|
||||||
@@ -635,7 +635,7 @@ batch_processor 15ms
|
|||||||
```
|
```
|
||||||
|
|
||||||
------
|
------
|
||||||
Отслеживать время жизни ссылок в асинхронном коде вручную крайне тяжело. Автоматика, как в Rust, делает это намного лучше, но при этом может выдавать совершенно непонятные репорты, в которых можно разобраться только если знаешь, что именно могло пойти не так — за это async в Rust и не любят и критикуют. А в качестве самого простого и продуктивного решения, чтоб убложить borrow checker, выбирается копирование всего подряд (`.clone()`, везде `.clone()`)
|
Отслеживать время жизни ссылок в асинхронном коде вручную крайне тяжело. Автоматика, как в Rust, делает это намного лучше, но при этом может выдавать совершенно непонятные репорты, в которых можно разобраться только если знаешь, что именно могло пойти не так — за это async в Rust и не любят и критикуют. А в качестве самого простого и продуктивного решения, чтоб ублажить borrow checker, выбирается копирование всего подряд (`.clone()`, везде `.clone()`)
|
||||||
|
|
||||||
С++ отдает полный контроль вам с невероятной кучей неявных захватов ссылок! Делайте с ними что хотите и как хотите. Скомпилируется без проблем и проверок. Вы можете приложить ментальные усилия, отследить все ссылки и убедиться что объекты не умрут не вовремя. Либо можно отчаяться, прочитать гайдлайны и передавать все и всегда by value. Копируя и перемещая. Никаких ссылок.
|
С++ отдает полный контроль вам с невероятной кучей неявных захватов ссылок! Делайте с ними что хотите и как хотите. Скомпилируется без проблем и проверок. Вы можете приложить ментальные усилия, отследить все ссылки и убедиться что объекты не умрут не вовремя. Либо можно отчаяться, прочитать гайдлайны и передавать все и всегда by value. Копируя и перемещая. Никаких ссылок.
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ auto p = std::make_unique<Pair>(1, 5);
|
|||||||
|
|
||||||
```C++
|
```C++
|
||||||
// https://godbolt.org/z/fTM4n7nqz
|
// https://godbolt.org/z/fTM4n7nqz
|
||||||
auto p = std::make_unique<Pair>(1, 5); // теперь компилирутся в C++20
|
auto p = std::make_unique<Pair>(1, 5); // теперь компилируется в C++20
|
||||||
```
|
```
|
||||||
|
|
||||||
Ну добавили и добавили... Стало же удобнее? Можно теперь всегда круглые скобки использовать?...
|
Ну добавили и добавили... Стало же удобнее? Можно теперь всегда круглые скобки использовать?...
|
||||||
@@ -90,7 +90,7 @@ struct Widget {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Эта строчка после вашей оптимизации продолжает молча компилироваться
|
// Эта строчка после вашей оптимизации продолжает молча компилироваться
|
||||||
// но теперь влечет неопределенное поведенине
|
// но теперь влечет неопределенное поведение
|
||||||
// пример: https://gcc.godbolt.org/z/q73erhYWs
|
// пример: https://gcc.godbolt.org/z/q73erhYWs
|
||||||
auto parent_widget = std::make_unqiue<Widget>(read_config());
|
auto parent_widget = std::make_unqiue<Widget>(read_config());
|
||||||
// И статические анализаторы пока молчат https://gcc.godbolt.org/z/aMsT3afxb
|
// И статические анализаторы пока молчат https://gcc.godbolt.org/z/aMsT3afxb
|
||||||
@@ -119,7 +119,7 @@ int main() {
|
|||||||
|
|
||||||
## Значения по умолчанию для ссылочных полей
|
## Значения по умолчанию для ссылочных полей
|
||||||
|
|
||||||
Закончить, пожалуй, нужно еще одним недоразуменинем с ссылочными полями. Им же можно задать значения по умолчанию...
|
Закончить, пожалуй, нужно еще одним недоразумением с ссылочными полями. Им же можно задать значения по умолчанию...
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
struct Config {
|
struct Config {
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ test_default_getter(m, 42, []{ return "default"sv; });
|
|||||||
```
|
```
|
||||||
`const string& : string_view` — первый неявно приводим ко второму. Взятие string_view от string происходит без копирования. Все отлично... И вроде безопасно.
|
`const string& : string_view` — первый неявно приводим ко второму. Взятие string_view от string происходит без копирования. Все отлично... И вроде безопасно.
|
||||||
|
|
||||||
А что если наша таблица с конфигурацией отпимизирована хранить string_view на части одной большой json-конфигурационной строки и ключа в ней нет?
|
А что если наша таблица с конфигурацией оптимизирована хранить string_view на части одной большой json-конфигурационной строки и ключа в ней нет?
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
using RefMap = std::map<int, std::string_view>;
|
using RefMap = std::map<int, std::string_view>;
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ usummate_squares(unsigned long): # @usummate_squares(unsigned l
|
|||||||
*/
|
*/
|
||||||
```
|
```
|
||||||
|
|
||||||
GCC 13 на данный момент *(2024 год)* в принципе [не делает](https://godbolt.org/z/xjf7zj768) таких оптимизаций по умолчанию. При этом последнии версии Clang 18
|
GCC 13 на данный момент *(2024 год)* в принципе [не делает](https://godbolt.org/z/xjf7zj768) таких оптимизаций по умолчанию. При этом последние версии Clang 18
|
||||||
уже способны свернуть цикл суммирования квадратов и для беззнаковых:
|
уже способны свернуть цикл суммирования квадратов и для беззнаковых:
|
||||||
|
|
||||||
```asm
|
```asm
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# ODR-violation и разделяемые библиотеки
|
# ODR-violation и разделяемые библиотеки
|
||||||
|
|
||||||
[Ранее](odr_violation.md) я рассматривал ODR-violation в общих чертах и предупреждал о том, что может произойти, если случайно выбрать не то имя переменной, структуры или функции в C++. В этой же части я бы хотел продемострировать более изящный пример, не требующий приложения никаких усилий по написанию кривого кода. Достаточно просто иметь кривой код в ваших third-party зависимостях.
|
[Ранее](odr_violation.md) я рассматривал ODR-violation в общих чертах и предупреждал о том, что может произойти, если случайно выбрать не то имя переменной, структуры или функции в C++. В этой же части я бы хотел продемонстрировать более изящный пример, не требующий приложения никаких усилий по написанию кривого кода. Достаточно просто иметь кривой код в ваших third-party зависимостях.
|
||||||
|
|
||||||
Недавно я имел дело со странным баг-репортом:
|
Недавно я имел дело со странным баг-репортом:
|
||||||
|
|
||||||
Во внутренем репозитории с пакетами обновилcя пакет с библиотекой [gtest](https://github.com/google/googletest) -- известная уважаемая библиотека для написания самых разных тестов на C++. И в результате обновления некоторые тесты в конечных приложениях стали внезапно падать.
|
Во внутреннем репозитории с пакетами обновилcя пакет с библиотекой [gtest](https://github.com/google/googletest) -- известная уважаемая библиотека для написания самых разных тестов на C++. И в результате обновления некоторые тесты в конечных приложениях стали внезапно падать.
|
||||||
|
|
||||||
Падать они стали по-разному. У одних стали валиться проверяющие ассерты. У других же все работало, проверки проходили, но [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) рапортовал что тестирующий процесс вышел с ненулевым кодом возврата.
|
Падать они стали по-разному. У одних стали валиться проверяющие ассерты. У других же все работало, проверки проходили, но [ctest](https://cmake.org/cmake/help/latest/manual/ctest.1.html) рапортовал что тестирующий процесс вышел с ненулевым кодом возврата.
|
||||||
|
|
||||||
@@ -190,7 +190,7 @@ Segmentation fault (core dumped)
|
|||||||
```
|
```
|
||||||
|
|
||||||
Ура! Падает!
|
Ура! Падает!
|
||||||
Обе библиотеки имеют свою собсвенную версию глобальной переменной с одним и тем же неявно экспортируемым именем. Использоваться опять-таки будет только **одна**.
|
Обе библиотеки имеют свою собственную версию глобальной переменной с одним и тем же неявно экспортируемым именем. Использоваться опять-таки будет только **одна**.
|
||||||
После загрузки библиотеки, после конструирования глобальной переменной, стандарт C++ требует зарегистрировать (например через `__cxa_atexit`) функцию для вызова деструктора. У нас две библиотеки -- значит две функции будут вызваны. На одном и том же объекте. Double free. Конструктор, кстати, также вызывается дважды по одному и тому же адресу:
|
После загрузки библиотеки, после конструирования глобальной переменной, стандарт C++ требует зарегистрировать (например через `__cxa_atexit`) функцию для вызова деструктора. У нас две библиотеки -- значит две функции будут вызваны. На одном и том же объекте. Double free. Конструктор, кстати, также вызывается дважды по одному и тому же адресу:
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Команде однажды завели баг-репорт: "Сервис упал c segmentation fault, в core dump стэк-трейс указывает как последнюю функцию перед падением что-то из вашей библиотеки. Разберитесь!". Упал сервис ровно один раз за полгода.
|
Команде однажды завели баг-репорт: "Сервис упал c segmentation fault, в core dump стэк-трейс указывает как последнюю функцию перед падением что-то из вашей библиотеки. Разберитесь!". Упал сервис ровно один раз за полгода.
|
||||||
|
|
||||||
Этим чем-то был вызов `free` где-то глубоко-глубоко внутри библиотери Protobuf. И несколько последующих стэк-фреймов указывали на вызов деструктора уже в нашей библиотеке. Потратив некоторое время на анализ кода деструктора, дежурный инженер не нашел ничего подозрительного и предположил, что это похоже на какую-то ранее встреченную проблему в Protobuf. И как воспроизвести никто не представлял. Тупик...
|
Этим чем-то был вызов `free` где-то глубоко-глубоко внутри библиотеки Protobuf. И несколько последующих стэк-фреймов указывали на вызов деструктора уже в нашей библиотеке. Потратив некоторое время на анализ кода деструктора, дежурный инженер не нашел ничего подозрительного и предположил, что это похоже на какую-то ранее встреченную проблему в Protobuf. И как воспроизвести никто не представлял. Тупик...
|
||||||
|
|
||||||
Я заинтересовался этой загадочной историей и залез в core dump поглубже.
|
Я заинтересовался этой загадочной историей и залез в core dump поглубже.
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ public:
|
|||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
Пользователи интерфейса нареализовывали свойх имплементаций. Все были счастливы, пока кто-то не сделал асинхронную реализацию. С ней почему-то приложение стало падать. Проведя небольшое расследование, вы выяснили, что пользователи интерфейса не позаботились вызвать метод `stop()` перед разрушением объекта. Какая досада!
|
Пользователи интерфейса нареализовывали своих имплементаций. Все были счастливы, пока кто-то не сделал асинхронную реализацию. С ней почему-то приложение стало падать. Проведя небольшое расследование, вы выяснили, что пользователи интерфейса не позаботились вызвать метод `stop()` перед разрушением объекта. Какая досада!
|
||||||
|
|
||||||
Вы были уставши и злы. А быть может это были и не вы, а какой-то менее опытный коллега, которому поручили доработать интерфейс. В общем, на свет родилась правка
|
Вы были уставши и злы. А быть может это были и не вы, а какой-то менее опытный коллега, которому поручили доработать интерфейс. В общем, на свет родилась правка
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ int32_t foo(int32_t x, int32_t y) {
|
|||||||
```
|
```
|
||||||
При вызове функции `foo` на стеке, после аргументов (хотя не факт что аргументы будут переданы через стек), будет выделено (просто вершина стека будет сдвинута) еще 4 байта (а может быть и больше, кто ж знает, что там настроено у компилятора!) под переменную z. А может быть и не будет выделено (например, если компилятор оптимизирует переменную и сложит результат сразу в регистр `rax`). Дикая природа удивительна, неправда ли?
|
При вызове функции `foo` на стеке, после аргументов (хотя не факт что аргументы будут переданы через стек), будет выделено (просто вершина стека будет сдвинута) еще 4 байта (а может быть и больше, кто ж знает, что там настроено у компилятора!) под переменную z. А может быть и не будет выделено (например, если компилятор оптимизирует переменную и сложит результат сразу в регистр `rax`). Дикая природа удивительна, неправда ли?
|
||||||
|
|
||||||
Освобождается память со стека тоже автоматически. Причем уже не обычно, а всегда. Если только, конечно, вы случайно не сломали стек, не сделали чудовищную ассемблерную вставку и теперь адрес возврата не ведет куда-то не туда или не используете `attribute ( ( naked ) )`. Но, мне кажется, в этих случаях у вас куда более серьезные проблемы... Во всех остальных случаях память со стека освобожается автоматически. Потому, как известно, вот такой код порождает висячий указатель
|
Освобождается память со стека тоже автоматически. Причем уже не обычно, а всегда. Если только, конечно, вы случайно не сломали стек, не сделали чудовищную ассемблерную вставку и теперь адрес возврата не ведет куда-то не туда или не используете `attribute ( ( naked ) )`. Но, мне кажется, в этих случаях у вас куда более серьезные проблемы... Во всех остальных случаях память со стека освобождается автоматически. Потому, как известно, вот такой код порождает висячий указатель
|
||||||
|
|
||||||
```C
|
```C
|
||||||
int32_t* foo(int32_t x, int32_t y) {
|
int32_t* foo(int32_t x, int32_t y) {
|
||||||
@@ -78,7 +78,7 @@ int main() {
|
|||||||
Тут каждый вызов `alloca` не приводит к переполнению стека сам по себе. Но
|
Тут каждый вызов `alloca` не приводит к переполнению стека сам по себе. Но
|
||||||
если `use_alloca` будет заинлайнена компилятором по какой-либо причине, мы получим [SIGSEGV](https://godbolt.org/z/1xWsjqK4G)
|
если `use_alloca` будет заинлайнена компилятором по какой-либо причине, мы получим [SIGSEGV](https://godbolt.org/z/1xWsjqK4G)
|
||||||
|
|
||||||
Использование `alloca` и VLA крайне не рекомендуется. [man](https://man7.org/linux/man-pages/man3/alloca.3.html) упоминает случай, когда их использование может быть оправдано: ваш код полагается на setjmp/longjump и нормальный менеджмент динамически выделенной памяти можеь быть осложнен, а стек все равно будет очищен даже при longjmp. Не буду спрашивать, зачем оно вам...
|
Использование `alloca` и VLA крайне не рекомендуется. [man](https://man7.org/linux/man-pages/man3/alloca.3.html) упоминает случай, когда их использование может быть оправдано: ваш код полагается на setjmp/longjump и нормальный менеджмент динамически выделенной памяти может быть осложнен, а стек все равно будет очищен даже при longjmp. Не буду спрашивать, зачем оно вам...
|
||||||
|
|
||||||
|
|
||||||
alloca и vla действительно в среднем быстрее чем динамическая аллокация. Но если уж нужно действительно быстро, то вариант с преаллоцированным массивом или массивом фиксированной длины [получше будет](https://quick-bench.com/q/JWSPzPFknaSECE2W1fPiQvnEdGs)
|
alloca и vla действительно в среднем быстрее чем динамическая аллокация. Но если уж нужно действительно быстро, то вариант с преаллоцированным массивом или массивом фиксированной длины [получше будет](https://quick-bench.com/q/JWSPzPFknaSECE2W1fPiQvnEdGs)
|
||||||
|
|||||||
@@ -129,14 +129,14 @@ auto f = [](float a) -> float {
|
|||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
return integrate((float(*)(float))(f));
|
return integrate((float(*)(float))(f));
|
||||||
// комилируется и работает
|
// компилируется и работает
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
||||||
// https://godbolt.org/z/fqzdse1Ya
|
// https://godbolt.org/z/fqzdse1Ya
|
||||||
|
|
||||||
// ниблоиды в std чаще определяются так, а не с помощью лямбл
|
// ниблоиды в std чаще определяются так, а не с помощью лямбд
|
||||||
struct {
|
struct {
|
||||||
static float operator()(float x) {
|
static float operator()(float x) {
|
||||||
return x;
|
return x;
|
||||||
@@ -210,7 +210,7 @@ float integrate<float (*)(float)>(float (*)(float)):
|
|||||||
pxor xmm0, xmm0
|
pxor xmm0, xmm0
|
||||||
cvtsi2ss xmm0, ebx
|
cvtsi2ss xmm0, ebx
|
||||||
add ebx, 1
|
add ebx, 1
|
||||||
call rbp // ! нет информации о функии -- вызов по указателю
|
call rbp // ! нет информации о функции -- вызов по указателю
|
||||||
addss xmm0, DWORD PTR [rsp+12]
|
addss xmm0, DWORD PTR [rsp+12]
|
||||||
movss DWORD PTR [rsp+12], xmm0
|
movss DWORD PTR [rsp+12], xmm0
|
||||||
cmp ebx, 26
|
cmp ebx, 26
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ NULL-терминированные строки -- унаследованная
|
|||||||
В новых C-библиотеках стараться проектировать API, использующие пару указатель + длина, а не только лишь указатель на NULL-терминированную последовательность.
|
В новых C-библиотеках стараться проектировать API, использующие пару указатель + длина, а не только лишь указатель на NULL-терминированную последовательность.
|
||||||
|
|
||||||
От этого наследия страдают программы на всех языках, вынужденные взаимодействовать с C API.
|
От этого наследия страдают программы на всех языках, вынужденные взаимодействовать с C API.
|
||||||
Rust, например, использует отдельные типы `CStr` и `CString` для подобных строк и переход к ним из нормального кода всегда сопровоздается мучительными тяжелыми преобразованиями.
|
Rust, например, использует отдельные типы `CStr` и `CString` для подобных строк и переход к ним из нормального кода всегда сопровождается мучительными тяжелыми преобразованиями.
|
||||||
|
|
||||||
Использование NULL-терминатора встречается не только для текстовых строк. Так, например, библиотека [SRILM](http://www.speech.sri.com/projects/srilm/) активно использует 0-терминированные последовательности числовых идентификаторов, создавая этим дополнительные проблемы.
|
Использование NULL-терминатора встречается не только для текстовых строк. Так, например, библиотека [SRILM](http://www.speech.sri.com/projects/srilm/) активно использует 0-терминированные последовательности числовых идентификаторов, создавая этим дополнительные проблемы.
|
||||||
Семейство функций exec в Linux принимают NULL-терминированные последовательности указателей.
|
Семейство функций exec в Linux принимают NULL-терминированные последовательности указателей.
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ free(): invalid pointer
|
|||||||
Program terminated with signal: SIGSEGV
|
Program terminated with signal: SIGSEGV
|
||||||
```
|
```
|
||||||
|
|
||||||
`std::shared_ptr<Button>(this)` создает новый shared_ptr, который ничего знаеть не знает о существовании другого умного указателя, управляющего объектом. Что разумеется приводит к попытке повторного освобождения памяти: сначала одним указателем, затем другим. Что иногда даже может работать успешно... Неопределенное поведение все-таки!
|
`std::shared_ptr<Button>(this)` создает новый shared_ptr, который ничего знает не знает о существовании другого умного указателя, управляющего объектом. Что разумеется приводит к попытке повторного освобождения памяти: сначала одним указателем, затем другим. Что иногда даже может работать успешно... Неопределенное поведение все-таки!
|
||||||
|
|
||||||
Хорошо, давайте чинить. Забудем пока про конструктор. Попробуем хотя бы починить метод `click`.
|
Хорошо, давайте чинить. Забудем пока про конструктор. Попробуем хотя бы починить метод `click`.
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ event received
|
|||||||
widget valid
|
widget valid
|
||||||
```
|
```
|
||||||
|
|
||||||
Осталось пойти и сделать конструтор приватным с помощью приватного-тэга. Ведь иначе кто-нибудь обязательно создаст кнопку на стэке. От чего либо наполучает нулевых указателей
|
Осталось пойти и сделать конструктор приватным с помощью приватного-тэга. Ведь иначе кто-нибудь обязательно создаст кнопку на стэке. От чего либо наполучает нулевых указателей
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
int main() {
|
int main() {
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ std::function<R(Args)> f = /* все что угочно,
|
|||||||
и результатом будет R */
|
и результатом будет R */
|
||||||
```
|
```
|
||||||
|
|
||||||
Благодаря технике _type-erasure_ (стирание типа), `std::function` может хранить в себе что угодно. У этого, конечно, есть цена — посредственная производительность: конкретный вызываемый объет должен быть перемещен в кучу, выделение памяти, динамическая диспетчеризация вызова... Если мы не пишем чего-то высоконагруженного, то цена не очень высока.
|
Благодаря технике _type-erasure_ (стирание типа), `std::function` может хранить в себе что угодно. У этого, конечно, есть цена — посредственная производительность: конкретный вызываемый объект должен быть перемещен в кучу, выделение памяти, динамическая диспетчеризация вызова... Если мы не пишем чего-то высоконагруженного, то цена не очень высока.
|
||||||
|
|
||||||
Однако, благодаря тому же самомуму стиранию типов и тому, как оно реализовано, `std::function` обладает еще некоторыми потрясающими спецэффектами!
|
Однако, благодаря тому же самому стиранию типов и тому, как оно реализовано, `std::function` обладает еще некоторыми потрясающими спецэффектами!
|
||||||
|
|
||||||
### Спецэффект 1. Вариантность
|
### Спецэффект 1. Вариантность
|
||||||
|
|
||||||
@@ -107,7 +107,7 @@ std::function<void(Keyboard*)> junion = senior;
|
|||||||
|
|
||||||
```C++
|
```C++
|
||||||
std::function<void(InputDevice*)> senior = [](auto){}; // Аллокация и перемещение лямбды на кучу! Стерли тип лямбды внутри
|
std::function<void(InputDevice*)> senior = [](auto){}; // Аллокация и перемещение лямбды на кучу! Стерли тип лямбды внутри
|
||||||
std::function<void(Keyboard*)> junion = std::move(senior); // Типы разные. Шаблоны инвариантны. Еще одна аллокация! и перемещенине исходной std::function на кучу. Стираем ее тип.
|
std::function<void(Keyboard*)> junion = std::move(senior); // Типы разные. Шаблоны инвариантны. Еще одна аллокация! и перемещение исходной std::function на кучу. Стираем ее тип.
|
||||||
```
|
```
|
||||||
А если цепочки передачи таких функций с изменением типов будут более длинными — становится уже не так здорово.
|
А если цепочки передачи таких функций с изменением типов будут более длинными — становится уже не так здорово.
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ Program terminated with signal: SIGSEGV
|
|||||||
```
|
```
|
||||||
И разумеется при наличии таких цепочек сохранять переданные в аргументах ссылки становится особенно сомнительным занятием.
|
И разумеется при наличии таких цепочек сохранять переданные в аргументах ссылки становится особенно сомнительным занятием.
|
||||||
|
|
||||||
Проблему с переизбытком аллокаций C++26 пердлагает решать с помощью `std::function_ref` — невладеющих ссылок на вызаваемый объект. Создайте ваш объект один раз и храните, а дальше передавайте на него ссылку с удобным интерфейсом. Вариантность остается в комплекте с возможностью получить dangling reference на другой `std::function_ref` в цепочке присваиваний.
|
Проблему с переизбытком аллокаций C++26 предлагает решать с помощью `std::function_ref` — невладеющих ссылок на вызаваемый объект. Создайте ваш объект один раз и храните, а дальше передавайте на него ссылку с удобным интерфейсом. Вариантность остается в комплекте с возможностью получить dangling reference на другой `std::function_ref` в цепочке присваиваний.
|
||||||
|
|
||||||
### Спецэффект 2. Отломанный const
|
### Спецэффект 2. Отломанный const
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# transform | filter — zero-cost абстракция, за которую нужно платить дважды
|
# transform | filter — zero-cost абстракция, за которую нужно платить дважды
|
||||||
|
|
||||||
Когда С++20 анонсировал добавление `ranges` в стандарнтую библиотеку, вокруг было очень много обсуждений.
|
Когда С++20 анонсировал добавление `ranges` в стандартную библиотеку, вокруг было очень много обсуждений.
|
||||||
Кто-то радовался (как, например, я, по наивности), кто-то наоборот высказывал опасения насчет этой новой и якобы удобной функциональности. Особенно много возмущений и критики было со стороны разработчиков игр:
|
Кто-то радовался (как, например, я, по наивности), кто-то наоборот высказывал опасения насчет этой новой и якобы удобной функциональности. Особенно много возмущений и критики было со стороны разработчиков игр:
|
||||||
|
|
||||||
- Эти шаблоны на шаблонах компилируются долго! (и это правда)
|
- Эти шаблоны на шаблонах компилируются долго! (и это правда)
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
Пример с бэнчмарком `transform | filter` против обычного старого дедовского for-loop обсуждался на форумах, показывался на конференциях, ну и здесь тоже без него не обойдется.
|
Пример с бэнчмарком `transform | filter` против обычного старого дедовского for-loop обсуждался на форумах, показывался на конференциях, ну и здесь тоже без него не обойдется.
|
||||||
|
|
||||||
Как известно, [std::ranges примудали чтоб в C++ было удобнее реализовывать печать календаря](https://www.youtube.com/watch?v=8yV2ONeWXyI&t=1s), а также суммировать квадраты чисел. Календарь печатать слишком долго, поэтому будем суммировать квадраты.
|
Как известно, [std::ranges придумали чтоб в C++ было удобнее реализовывать печать календаря](https://www.youtube.com/watch?v=8yV2ONeWXyI&t=1s), а также суммировать квадраты чисел. Календарь печатать слишком долго, поэтому будем суммировать квадраты.
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
// Суммировать будем "маленькие" квадраты, чтоб как-то оправдать использование filter после transform
|
// Суммировать будем "маленькие" квадраты, чтоб как-то оправдать использование filter после transform
|
||||||
@@ -54,11 +54,11 @@ static int sum_small_squares_ranges(std::vector<int>& v) {
|
|||||||
|
|
||||||
----
|
----
|
||||||
|
|
||||||
Обычно в этом сборнике я овещаю случаи undefined и unspecified поведения. И очень редко какие-то well-defined вещи. Однако, ситуация с `transform | filter` сочетанием оказалась настолько неожиданной, что обойти ее стороной нельзя. И речь не о производительности.
|
Обычно в этом сборнике я оcвещаю случаи undefined и unspecified поведения. И очень редко какие-то well-defined вещи. Однако, ситуация с `transform | filter` сочетанием оказалась настолько неожиданной, что обойти ее стороной нельзя. И речь не о производительности.
|
||||||
|
|
||||||
Давайте попробуем другой пример, более приближенный к реальности. Ведь на C++ не только квадраты считают, но еще и какую-то бизнес-логику программируют.
|
Давайте попробуем другой пример, более приближенный к реальности. Ведь на C++ не только квадраты считают, но еще и какую-то бизнес-логику программируют.
|
||||||
|
|
||||||
Вы подготовили список API calls запросов к AWS и собиратесь их исполнить, какие-то могут завершиться ошибкой, какие-то выполниться успешно.
|
Вы подготовили список API calls запросов к AWS и собираетесь их исполнить, какие-то могут завершиться ошибкой, какие-то выполниться успешно.
|
||||||
|
|
||||||
Конструкция
|
Конструкция
|
||||||
`requests | transform(execute) | filter(succeded)` выглядит очень чисто, красиво и понятно.
|
`requests | transform(execute) | filter(succeded)` выглядит очень чисто, красиво и понятно.
|
||||||
@@ -184,7 +184,7 @@ https://github.com/ericniebler/range-v3/issues/1090
|
|||||||
|
|
||||||
Вернемся к началу. Цепочка `filter | transform` медленнее в 2 раза. Успешные реквесты выполняются два раза. Так и задумано. Из лучших побуждений. И из проблемного дизайна итераторов в C++.
|
Вернемся к началу. Цепочка `filter | transform` медленнее в 2 раза. Успешные реквесты выполняются два раза. Так и задумано. Из лучших побуждений. И из проблемного дизайна итераторов в C++.
|
||||||
|
|
||||||
`Range` `r` в современном C++ это абстактная штука, у которой есть `begin(r)`, возвращающий итератор `it`. И `end(r)`, возвращающий так называемый _sentinel_ `s` — еще одна абстрактная штука, с которой сравнивается итератор, чтоб понять, закончилась ли последовательность.
|
`Range` `r` в современном C++ это абстрактная штука, у которой есть `begin(r)`, возвращающий итератор `it`. И `end(r)`, возвращающий так называемый _sentinel_ `s` — еще одна абстрактная штука, с которой сравнивается итератор, чтоб понять, закончилась ли последовательность.
|
||||||
|
|
||||||
Так что процесс итерации по какому-либо range выглядит следующим образом
|
Так что процесс итерации по какому-либо range выглядит следующим образом
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ while (it != s) {
|
|||||||
2. Разыменование — извлечение элемента
|
2. Разыменование — извлечение элемента
|
||||||
3. Продвижение к следующему элементу
|
3. Продвижение к следующему элементу
|
||||||
|
|
||||||
Посмотрим, что пишут в стадарте про итератор `views::filter`
|
Посмотрим, что пишут в стандарте про итератор `views::filter`
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -219,7 +219,7 @@ current_ = ranges::find_if(std::move(++current_),
|
|||||||
return *this;
|
return *this;
|
||||||
```
|
```
|
||||||
|
|
||||||
Ага, то есть при продвижении итератора выполняется `find_if`, который разымeyет `++current_`, чтоб пременить к нему предикат фильтрации.
|
Ага, то есть при продвижении итератора выполняется `find_if`, который разымeyет `++current_`, чтоб применить к нему предикат фильтрации.
|
||||||
И потом мы его опять разыменуем, чтоб достать значение.
|
И потом мы его опять разыменуем, чтоб достать значение.
|
||||||
|
|
||||||
Вот оно удвоение!
|
Вот оно удвоение!
|
||||||
@@ -260,7 +260,7 @@ console.log(result); // Array [1, 1, 3]
|
|||||||
|
|
||||||
`flatMap` — довольно общая операция, трансформирующая каждый элемент последовательности в какую-то новую последовательность и объединяющая их в одну "плоскую" последовательность.
|
`flatMap` — довольно общая операция, трансформирующая каждый элемент последовательности в какую-то новую последовательность и объединяющая их в одну "плоскую" последовательность.
|
||||||
|
|
||||||
В стандартной библиотеке С++ непосредственно одного комбинатора `flatMap` (тут бы он неверняка назывался `flat_transform`) нету. Но того же эффекта можно достичь с помощью цепочки `transform | join`. Остенется лишь найти подходящий контейнер для представления последовательности из одного либо нуля элементов.
|
В стандартной библиотеке С++ непосредственно одного комбинатора `flatMap` (тут бы он наверняка назывался `flat_transform`) нету. Но того же эффекта можно достичь с помощью цепочки `transform | join`. Остенется лишь найти подходящий контейнер для представления последовательности из одного либо нуля элементов.
|
||||||
|
|
||||||
И такой контейнер есть — `std::optional`. В C++26 ему добавили методы `.begin()` и `.end()`. Этот диапазон состоит либо из одного элемента, если `optional` содержит объект, или из нуля, если не содержит. И его можно использовать для фильтрации элементов!
|
И такой контейнер есть — `std::optional`. В C++26 ему добавили методы `.begin()` и `.end()`. Этот диапазон состоит либо из одного элемента, если `optional` содержит объект, или из нуля, если не содержит. И его можно использовать для фильтрации элементов!
|
||||||
|
|
||||||
@@ -427,7 +427,7 @@ address of grades after: 37319408 // ! same address, no copy
|
|||||||
|
|
||||||
```Rust
|
```Rust
|
||||||
// Rust вариант страдает от своих особенностей с лайфтаймами
|
// Rust вариант страдает от своих особенностей с лайфтаймами
|
||||||
// Которые частично рещаются с помощью Generic Associated Types
|
// Которые частично решаются с помощью Generic Associated Types
|
||||||
// Но как абстрактный пример сойдет
|
// Но как абстрактный пример сойдет
|
||||||
trait Iterator {
|
trait Iterator {
|
||||||
type Value;
|
type Value;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ int main() {
|
|||||||
|
|
||||||
Я не смог найти ни одного компилятора доступного онлайн, на котором бы можно было бы воспроизвести последовательность оптимизаций, приводящих к падению невероятной красоты. Но я видел несколько закрытых bug репортов в отношении Apple Clang, который такое проворачивал.
|
Я не смог найти ни одного компилятора доступного онлайн, на котором бы можно было бы воспроизвести последовательность оптимизаций, приводящих к падению невероятной красоты. Но я видел несколько закрытых bug репортов в отношении Apple Clang, который такое проворачивал.
|
||||||
|
|
||||||
LLVM может генерировать под x86 инструкцию `ud2` -- это недопустимая инструкция, часто используема как индикатор недостижимого кода. Если программа попытается ее выполнить, она умрет от сигнала SIGILL.
|
LLVM может генерировать под x86 инструкцию `ud2` -- это недопустимая инструкция, часто используемая как индикатор недостижимого кода. Если программа попытается ее выполнить, она умрет от сигнала SIGILL.
|
||||||
Код, который провоцирует неопределенное поведение может быть помечен как недостижимы и в дальнейшем заменен на `ud2` или выброшен.
|
Код, который провоцирует неопределенное поведение может быть помечен как недостижимы и в дальнейшем заменен на `ud2` или выброшен.
|
||||||
В нашем замечательном примере компилятору вполне известно, что `buffer.size() == 0`. И его не меняли.
|
В нашем замечательном примере компилятору вполне известно, что `buffer.size() == 0`. И его не меняли.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Атрибут [[assume]]
|
# Атрибут [[assume]]
|
||||||
|
|
||||||
"Есть некоротая вселенская несправедливость", — подумали в комитете стандартизации C++, — "мы так много всего в языке назначили быть неопределенным поведением, чтоб помочь компиляторам генерировать оптимальный код. Но не дали такую же стандартную возможность нашим пользователям — программистам!"
|
"Есть некоторая вселенская несправедливость", — подумали в комитете стандартизации C++, — "мы так много всего в языке назначили быть неопределенным поведением, чтоб помочь компиляторам генерировать оптимальный код. Но не дали такую же стандартную возможность нашим пользователям — программистам!"
|
||||||
|
|
||||||
Да, С++23 наконец-то дал простым пользователям инструмент целенаправленного **внедрения** неопределенного поведения в их код. Такой инструмент, правда, давно уже был и так, но специфичный для конкретного компилятора. C++23 же всего лишь стандартизировал его. Так что радуйтесь, никаких больше уродливых `__builtin_assume`!
|
Да, С++23 наконец-то дал простым пользователям инструмент целенаправленного **внедрения** неопределенного поведения в их код. Такой инструмент, правда, давно уже был и так, но специфичный для конкретного компилятора. C++23 же всего лишь стандартизировал его. Так что радуйтесь, никаких больше уродливых `__builtin_assume`!
|
||||||
|
|
||||||
@@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
На самом деле, конечно, причина есть: компиляторы глупые, быстрый и оптимальный код получить хочется, а на ассемблере писать не очень хочется. Хотя, конечно, разработчики ffmpeg с этим не согласятся — они поэтому целенаправленно делают ассемблерные вставки, не доверяя компиляторам С.
|
На самом деле, конечно, причина есть: компиляторы глупые, быстрый и оптимальный код получить хочется, а на ассемблере писать не очень хочется. Хотя, конечно, разработчики ffmpeg с этим не согласятся — они поэтому целенаправленно делают ассемблерные вставки, не доверяя компиляторам С.
|
||||||
|
|
||||||
Несмотря на то что мы говорим о C и C++, я позволю себе привести пример на Rust, поскольку считаю, что он наиболее ярко может продемонстрировать логику новвовведения С++23.
|
Несмотря на то что мы говорим о C и C++, я позволю себе привести пример на Rust, поскольку считаю, что он наиболее ярко может продемонстрировать логику нововведения С++23.
|
||||||
|
|
||||||
Возьмем достаточно простую функцию, которая выполняет семплирование отсортированной выборки: разбивает ее на группы равной величины и из каждой группы выбирает медианну величину
|
Возьмем достаточно простую функцию, которая выполняет семплирование отсортированной выборки: разбивает ее на группы равной величины и из каждой группы выбирает медианную величину
|
||||||
|
|
||||||
```Rust
|
```Rust
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ void sum(int count, /* чтобы получить доступ к списку
|
|||||||
|
|
||||||
C простой, маленький язык. В нем не так много типов. Примитивы, указатели, да пользовательские структуры.
|
C простой, маленький язык. В нем не так много типов. Примитивы, указатели, да пользовательские структуры.
|
||||||
|
|
||||||
В C++ есть ссылки. Есть объекты с интересными конструкторами и деструкторами. И вы уже наверняка догадались о том, что будет неопеределенное поведение, если засунуть ссылку или такой объект в качестве аргумента вариативной функции. Еще больше возможностей для веселой отладки!
|
В C++ есть ссылки. Есть объекты с интересными конструкторами и деструкторами. И вы уже наверняка догадались о том, что будет неопределённое поведение, если засунуть ссылку или такой объект в качестве аргумента вариативной функции. Еще больше возможностей для веселой отладки!
|
||||||
|
|
||||||
Но C++ не был бы самим собой, если бы в нем эту проблему не «решили». И так у нас есть C++-style вариадики:
|
Но C++ не был бы самим собой, если бы в нем эту проблему не «решили». И так у нас есть C++-style вариадики:
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ struct Unit {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Из-за константного поля объекты `Unit` теряют операцию присваивания.
|
Из-за константного поля объекты `Unit` теряют операцию присваивания.
|
||||||
Их нелья менять местами — `std::swap` не работает больше.
|
Их нельзя менять местами — `std::swap` не работает больше.
|
||||||
`std::vector<Unit>` нельзя больше просто так отсортировать... В общем, сплошное удобство.
|
`std::vector<Unit>` нельзя больше просто так отсортировать... В общем, сплошное удобство.
|
||||||
|
|
||||||
Но самое интересное начинается, если сделать что-то такое
|
Но самое интересное начинается, если сделать что-то такое
|
||||||
@@ -224,7 +224,7 @@ std::cout << unit.back().id << "";
|
|||||||
Компилятор имеет право воспринимать происходящее следующим образом:
|
Компилятор имеет право воспринимать происходящее следующим образом:
|
||||||
- В векторе 1 элемент
|
- В векторе 1 элемент
|
||||||
- Вектор не реаллоцировался.
|
- Вектор не реаллоцировался.
|
||||||
- Указатель на элемет в первом `cout` и во втором `cout` один и тот же.
|
- Указатель на элемент в первом `cout` и во втором `cout` один и тот же.
|
||||||
- И там и там используется константное поле
|
- И там и там используется константное поле
|
||||||
- Я его уже читал при первом `cout`
|
- Я его уже читал при первом `cout`
|
||||||
- Зачем мне его читать еще раз, это же константа!
|
- Зачем мне его читать еще раз, это же константа!
|
||||||
|
|||||||
@@ -217,7 +217,7 @@ int main() {
|
|||||||
m.set("Metric", val, std::move(comm)); // компилируется, как и хотели
|
m.set("Metric", val, std::move(comm)); // компилируется, как и хотели
|
||||||
m.set("MName", val, std::string_view("comment")); // не компилируется, хорошо
|
m.set("MName", val, std::string_view("comment")); // не компилируется, хорошо
|
||||||
auto gen_comment = []()->std::string { return "comment"; };
|
auto gen_comment = []()->std::string { return "comment"; };
|
||||||
m.set("MName", val, gen_comment()); // рабоатет отлично
|
m.set("MName", val, gen_comment()); // работает отлично
|
||||||
}
|
}
|
||||||
// https://godbolt.org/z/zjWGWY4xh
|
// https://godbolt.org/z/zjWGWY4xh
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ int main() {
|
|||||||
std::cout << (5 * OptionalPositive { 5 });
|
std::cout << (5 * OptionalPositive { 5 });
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
Это компилируется и выдает результат `6005`. Потому как выполняется user-defined неявное приведенине к `bool`, который далее неявно приводится к `int`. Все правильно.
|
Это компилируется и выдает результат `6005`. Потому как выполняется user-defined неявное приведение к `bool`, который далее неявно приводится к `int`. Все правильно.
|
||||||
|
|
||||||
Последние версии Clang хотя бы вывают частично [предупреждения](https://gcc.godbolt.org/z/fs98G3o3f)
|
Последние версии Clang хотя бы вывают частично [предупреждения](https://gcc.godbolt.org/z/fs98G3o3f)
|
||||||
```
|
```
|
||||||
@@ -86,9 +86,9 @@ int main() {
|
|||||||
Правда, если поменять тип константы слева на `double`, в Clang 19. предупреждение исчезнет. Но компилироваться оно [не перестанет](https://gcc.godbolt.org/z/MhafPcTvv).
|
Правда, если поменять тип константы слева на `double`, в Clang 19. предупреждение исчезнет. Но компилироваться оно [не перестанет](https://gcc.godbolt.org/z/MhafPcTvv).
|
||||||
|
|
||||||
Никогда, если только у вас не C++98, не определяйте неявный `operator bool`! Он всегда должен быть `explicit`. Если вы боитесь, что это заставит вас делать `static_cast<bool>` там, где этого не хочется делать, то не переживайте!
|
Никогда, если только у вас не C++98, не определяйте неявный `operator bool`! Он всегда должен быть `explicit`. Если вы боитесь, что это заставит вас делать `static_cast<bool>` там, где этого не хочется делать, то не переживайте!
|
||||||
С++ определяет несколько контекстов, в которых `explicit operator bool` все равно может быть вызван неявно: в условиях `if`, `for` и `while`, а также в логических операциях. Этого достаточно для большинства использнований `operator bool`.
|
С++ определяет несколько контекстов, в которых `explicit operator bool` все равно может быть вызван неявно: в условиях `if`, `for` и `while`, а также в логических операциях. Этого достаточно для большинства использований `operator bool`.
|
||||||
|
|
||||||
Если у вас C++98... Я вам очень соболезную. Но и даже в вашем печальном случае есть решение. Чудовищно громоздкое, но решение — можете ознакомиться с устаревшей [Safe Bool Idiom](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool) в свободное время в качестве домашнего задания. Если коротко, вместо `operator bool` предгалалось определить
|
Если у вас C++98... Я вам очень соболезную. Но и даже в вашем печальном случае есть решение. Чудовищно громоздкое, но решение — можете ознакомиться с устаревшей [Safe Bool Idiom](https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Safe_bool) в свободное время в качестве домашнего задания. Если коротко, вместо `operator bool` предлагалось определить
|
||||||
|
|
||||||
```C++
|
```C++
|
||||||
// Указатель на метод в приватном классе!
|
// Указатель на метод в приватном классе!
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ fn add(x: i32, y: i32) -> i32 {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Обоснования, почему не обязательно писать в конце функции `return`, следующие:
|
Обоснования, почему не обязательно писать в конце функции `return`, следующие:
|
||||||
1. В функции может быть ветвление логики. В одной из веток может вызываться код, который не предполагает возврата: бесконечный цикл, исключение, `std::exit`, `std::longjmp` или что-то иное, помеченное аттрибутом `[[noreturn]]`. Проверить на наличие такого кода не всегда возможно.
|
1. В функции может быть ветвление логики. В одной из веток может вызываться код, который не предполагает возврата: бесконечный цикл, исключение, `std::exit`, `std::longjmp` или что-то иное, помеченное атрибутом `[[noreturn]]`. Проверить на наличие такого кода не всегда возможно.
|
||||||
2. Функция может содержать ассемблерную вставку со специальным кодом финализации и инструкцией `ret`.
|
2. Функция может содержать ассемблерную вставку со специальным кодом финализации и инструкцией `ret`.
|
||||||
|
|
||||||
Проверить наличие формального `return`, конечно, можно. Но нам разрешили не писать иногда (очень иногда!) чисто формальную строчку, а компиляторам разрешили не считать это ошибкой.
|
Проверить наличие формального `return`, конечно, можно. Но нам разрешили не писать иногда (очень иногда!) чисто формальную строчку, а компиляторам разрешили не считать это ошибкой.
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ template <class T>
|
|||||||
struct STagged {};
|
struct STagged {};
|
||||||
|
|
||||||
|
|
||||||
using S1 = STagged<struct Tag1>; // преобъявление струкруты Tag1
|
using S1 = STagged<struct Tag1>; // предобъявление струкруты Tag1
|
||||||
using S2 = STagged<struct Tag2*>; // преобъявление струкруты Tag2
|
using S2 = STagged<struct Tag2*>; // предобъявление струкруты Tag2
|
||||||
|
|
||||||
void fun(struct Tag3*); // предобъявление структуры Tag3
|
void fun(struct Tag3*); // предобъявление структуры Tag3
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ int main() {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Clang способен предепреждать о подобном.
|
Clang способен предупреждать о подобном.
|
||||||
|
|
||||||
С++11 и новее предлагают *universal initialization* (через `{}`), которая не совсем *universal* и имеет свои проблемы.
|
С++11 и новее предлагают *universal initialization* (через `{}`), которая не совсем *universal* и имеет свои проблемы.
|
||||||
C++20 предлагает еще одну *universal* инициализацию, но уже снова через `()`...
|
C++20 предлагает еще одну *universal* инициализацию, но уже снова через `()`...
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ struct Matrix {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// И вот месяц назад вы добавили восхитетельную
|
// И вот месяц назад вы добавили восхитительную
|
||||||
// перегрузку для доступа к элементу.
|
// перегрузку для доступа к элементу.
|
||||||
// Библиотека используется с разными версиями C++, так что
|
// Библиотека используется с разными версиями C++, так что
|
||||||
// перегрузка под feature-control флагом -- все отлично
|
// перегрузка под feature-control флагом -- все отлично
|
||||||
|
|||||||
Reference in New Issue
Block a user