Как отказаться от динамического распределения памяти в C++
Здравствуйте, я Данил Демидов.
Работаю в компании «Ауриго».
Занимаюсь страймой системами, в основном на C++.
И также рассказываю об этом в Телеграм-канале C++ Embedded.
И вообще в «Ауриге» очень любят Embedded проекты.
Мы делаем не только какие-то промышленные устройства для безопасности, но и медицинские устройства.
Также, насколько я помню, Маурига также приняла участие в создании международного стандарта по разработке ПО для медицинских устройств.
То есть, некоторые опыты у нас все-таки есть.
И о чем хотелось сказать сегодня.
то чем мы руководствуемся, когда пишем программное беспечение?
Какие стандарты спецификации используем?
И какие особенности в коде из-за этого мы привносим?
Почему лучше не использовать динамическое распределение памяти?
Как мы ловим динамическое распределение на кучей?
А также печальная история о том, как мы потеряли исключение, что делается стандартными классами без библиотеки, стандартных шаблонов, а также могут ли нам помочь аллокаторы вернуть их.
До ещё о Вриге самым серьёзным РММ-проектом была автоматическая кормушка для мошеных животных.
Когда мы ее писали, мы руководствовались исключительно здравым смыслом, ну и, может быть, книжкой про чистый код, но этого стратически мало.
Для стандартов безопасности, если вы делаете какое-то серьезное устройство, которое следит за опасными процессами, конечно, нужно что-то посерьезнее.
Также подумала и международная электротехническая комиссия, и в конце прошлого века
Дала нам стандарт 61508 для промышленных устройств.
Там она вводит в частности параметр уровень полноты безопасности.
Этот параметр показывает нам, насколько устройства надежны.
Всего есть четыре уровня.
сил, так называемый Safety Integrity Level.
И уровень первый, это там самое безответственное.
Меньше всего предъявляют к нему требований.
Потому что в случае сбоя этого оборудования единственное, чем он может повредить, это самооборудование или продукция.
Но и все равно допустимое число отказов.
Это один отказ на 100 миллионов часов.
Это примерно один отказ в 10 лет.
Ну довольно надежно.
Уровень сил 2 и сил 3 – это уже посерьезнее оборудование, потому что накануло здоровье и жизнь персонала человека.
И уровень сил 4 – это самое надежное оборудование, потому что в случае сбоя произойдет технологенная катастрофа.
И надежность такого оборудования – это один отказ на 100, на 100, на 10 тысяч лет.
То есть практически никогда оно не должно отказывать.
Вообще, стандартов есть дикое количество для разных отраслей свои стандарты, но объединяет их в плане разработки программного обеспечения на CEC++ стандарт мисера.
То есть он покрывает все стандарты.
И какие же это привносит особенности в код?
Вообще для проекта могут определяться разные требования, которые сильно изменят ваш си плюс-плюс.
Например, я разговаривал с разработчиком телекомоборудования.
Он говорит, что наша главная цель — это производительность.
Поэтому мы все оттуда убрали лишнее.
У нас есть только си с классами.
Все эти ваши новые возможности языка не только вредят производительности.
Давайте уберем.
Не хотелось бы дойти до этого.
Посмотрим, что можно оставить для безопасности.
Итак, такие особенности, которые вспоминаются сразу, запрещена рекурсия.
Я думаю, понятно, потому что стек может закончиться прямо быстро, если вы неправильно используете рекурсии.
Арифметику-казатель ограничена, она применима только для массивов, для индексации элементов массивов.
Вообще любое неявное преобразование, на него как бы стадарты безопасности и косы смотрят.
Вообще их не рекомендуется и лучше не использовать.
И все предыдущие пункты, а также многие другие анализируются, применяется статистический анализ, да, применяется несколько
анализаторов, это может быть какой-то общий, потом специфический для миссера, для автосар, и ни одной из предупреждений нельзя игнорировать.
То есть вы должны либо исправить его, либо объяснить, почему вы это делаете, почему именно так.
И такое объяснение отдел, как бы одобрения, должен найти состоятельным.
И теперь самые интересные особенности.
Первое про долгожителей.
Только долгосрочные объекты.
Я думаю, такие переменные есть в любой программе, которая живут на протяжении всей работы устройства.
А устройство может работать очень долго.
Это годы и для десятилетия.
И неизвестно, что может случиться.
То есть какое-нибудь космическое излучение повредит участок памяти, инвертирует хотя бы один бит.
И вот мы уже получаем переменное непонятное, неправильное значение, которое не имеет смысла.
И устройство может работать очень неправильно из-за этого.
что предлагается использовать обертку.
Например, save storage структура.
В ней есть метод save, чтобы сохранить значение переменной, как бы оригинальный переменной, или load, чтобы получить его обратно.
И что мы видим внутри.
Наша исходная переменная, вот она в памяти.
Здесь ничего сложного, но как определить, что память повреждена?
Ну, если вспомнить помеху сточьего кодирования и использовать какие-то методы оттуда, нам нужна некая дополнительная информация для этого.
Это могут быть записаны кодой хэмминга или код Рида Соломона.
в последние даже достался возможности исправлять некоторые ошибки в некоторых случаях но самый простой и самый распространенный вариант это использование просто дополнительного буфера куда мы записываем копию нашей переменной только инвертированную побитого и тогда при вызове функции load мы обязательно проверим соответственно наш оригинал негативу ну также проверок можно производить периодически
И вторая особенность — контроль математических операций, потому что операции с целыми числами не должны вести к потере данных.
Есть, например, такая простая функция.
Видно, что она возвращается в сумму двух переменных типа int8.
И в обычной жизни мы просто предполагаем, что эти числа достаточно малые, чтобы не вызвать переполнения.
Но безопасность требует каких-то дополнительных действий не только наше соображение.
И что она предлагает, давайте проверим заранее перед операцией диапазона этих переменных.
неплохая мысль, но, допустим, если у вас уже много операций, вы хотите соответствовать стандарту, вы получили много таких ошибок, и как бы это тяжело исправить, особенности, если у вас нет каких-то соображений, в каком диапазоне должны быть эти применные.
Мы для этих случаев написали специальные функции для безопасного сложения, вычитания и умножения.
что облегчило нам работа, то есть мы операции прозменяем на безопасную функцию.
Внутри у нее просто вызов статической какой-то функции, куда мы передаем тип операций значений переменных.
Мы проверяем корректность диапазона, исходя из типа и значения переменных.
Если все в порядке, тогда переходим, кстати, к касту.
То есть мы преобразуем их обратно в тип T. Почему обратно?
Потому что небольшие типы, из каранговые, они из-за свойства Integral Promotion, преобразуются в винт.
И это есть как бы неявное преобразование, которое должно стать явным.
и для почти любого типа у нас преобразование идет именно к n2.
То есть можно вот вычислить сумму двух переменных, одно из которых 100, другой 28.
В результате будет сумма 128, но это только потому, что оно типа int.
Если мы захотим преобразовать его назад в int 8, тогда мы получим минус 128.
Думаю, это везде знает.
И как же реализована функция из коррект?
Все просто.
Здесь мы выбираем, какую функцию мы вызываем для того, чтобы сформировать наш интервал.
И на основе значения первой переменной мы как раз на основе типа и значение первой переменной мы выстраиваем интервал для второй переменной.
И в конце просто проверяем, попадает ли вторая переменная в наш интервал.
То есть, как это происходит, какая тут математика довольно простая.
То есть допустимый интервал для первой переменной это любое значение в пределах типа.
Для nt8 это от минус 128 до 127.
Если первое переменное у нас значение минус 28, мы просто сдвигаем границу нижнюю границу вверх.
Итоговый интервал у нас получается минус 100, 127.
В значение второе переменное 50, мы подаем в эту границу, все хорошо.
Как это выглядит в коде?
Довольно отревельно.
То есть, с помощью сфинаи отрезаем, мы вызываем ее только для знаковых типов, формируем базовый интервал и работаем с границами.
Поднимаем нижнюю границу либо опускаем верхнюю.
Для беззнаковых переменных точно такая же процедура с одним изъятием.
Мы работаем только с верхним оторвалом, мы его понижаем.
В коде это видно.
И третья особенность.
Хотя нет, одно замечание.
Наверное, многие подумают, может, это излишне.
Но, определив эти безопасные функции, мы в процессе работы несколько раз натыкались на переполнение где-то в неожиданных местах.
То есть это вполне себе помогает, даже в тестах выявить это.
И почему?
Третья особенность – отказ от динамического распыления памяти.
Вообще, это требование не настолько обязательное, только если вы работаете с оборудованием сил 3 и сил 4.
Мы как раз таким и работали.
Для остальных это опционально.
В мисре есть однозначное правило, что выделение динамическое на куче запрещено.
Не должно быть использовано.
Потому что это чревато утечками памяти, фрагментации памяти, некорректным доступом.
То есть мы где-то освободили память, кто-то еще на нее ссылается.
Перерасход памяти это особенно актуально для нас проблема.
У нас немного ресурсов в этих устройствах.
и непредсказуемое время исполнение, но оно связано с тем, что динамическая локация работает дольше, чем статическая.
И почему за этим нужно следить?
В проект относительно легко можно вынести динамическую локацию.
Например, мы хотим использовать какую-то интерфейс.
Он принимает либо массив чаров, либо стринг.
И мы хотим использовать первую функцию.
Ну, забыли про второй параметр.
И здесь автоматически произойдет преобразование строк у литералов стадостринг.
Строка достаточно большая, и она вызывает динамическую локацию, потому что ей не хватит памяти внутреннего буфера.
Если вдруг кто-то в проекте вспомнит про функцию startup и использует ее зачем-то, потребовалась человеку копии строки,
И к чему это приведет, конечно же, возмумолок, потому что дубликат строки, он разместит на памяти, которую вызовет динамически.
Как же за этим проследить?
Это нужно обнаружить.
Проект нужно оградить от этого.
Вообще, какой у нас проект?
Чтобы было понятно.
Разработка у нас бармэттл.
Нет никаких операционных систем.
Компиляторы использования GCC.
Тут понятно AR, потому что его любят наши коллеги.
И стандарт хотелось бы использовать C++ последнего стандарта, но, к сожалению, мы ограничены именно компилятором IAR.
То есть он поддерживает C++ 17 с некоторыми зьятиями.
И результат.
У нас это просто L-файл, на основе которого мы генерируем образ там прошивки.
или другой какой-то версии.
Так, и кодовая база, ну на самом деле, раскроем карты, это успешный прототип.
Мы просто решили как бы привести его к соответствию стандартам безопасности, поэтому там много странного встречается.
архитектура, так называемая superloop.
У нас нет никаких много поточностей.
У нас есть только superloop.
Что это значит?
Устройство стартует, попадает в функцию main.
Там происходит короткая инцелизация.
Мы выставляем нужные значения нашим объектам и уходим в бесконечный цикл.
Там он пробегается по таскам, на самом деле это функции, это не таски.
И подает дисп слип, чтобы сэкономить немного энергии.
Это такая своеобразная кооперативная многозадачность.
И как это выглядит в коде?
Довольно просто.
У нас есть статический объект монитора.
У нас есть функция main.
И в блоке try мы инициализируем наш монитор, запускаем его.
Дальше мы уходим в бесконечный цикл.
Тут должны быть таски.
Но есть только deep sleep.
Еще страшного.
Если вдруг что-то пойдет не так, мы перехватываем исключение и попадаем в save state.
Так-то с вами безопасная режима, где сохраняется минимальное функциональное устройство, но еще мы можем знать, почему же мы оказались в этом режиме.
Как выглядит монитор?
Он нарочит как бы простой.
Но многословный.
Здесь у нас есть блок диагностической информации.
Сколько мы алацировали, диалацировали.
Это лишь пример.
Он немного сложнее.
Все функции, которые манипулируются памятью, добавлены в друзья.
и две функции публичные, то есть чтобы запустить и остановить монитор.
Это, ну, остановить монитор может потребоваться в тестах, к примеру, где у тестов framework есть возможность переигрывать с динамической памятью.
В этом нет ничего страшного, это допустимо.
И как мы перегружаем оператор new, довольно просто, если монитор выключен, мы просто собираем диагностическую информацию и вызываем malloc.
чтобы не нарушить выделение памяти, если вдруг понадобится.
А если монитор включен, наоборот, тогда тут нужно дать знать, что происходит что-то непонятное, что-то странное.
Вообще операторов не много.
Мы сделаем вид, что у нас есть только базовый.
Также, да, нужно переопределить оператор delete.
Здесь мы видим, что освобождаем память, собираем диагностическую информацию.
Все ли это?
Нет, это не все.
Если не ошибаюсь, 2014-го стандарта у нас по умолчанию называется размерный, так сказать, оператор delete, вторым параметром, которого выступает, передается,
количество освобождаем памяти.
И это как раз основная проблема.
Потому что есть правила безопасности, что если мы перегружаем его, то, пожалуйста, перегружите и без размерной длит, который идет без этого параметра.
Почему?
Потому что это может нарушить нашу диагностику.
В самом простом случае мы выделяем пратром его объект, мы знаем его размер, мы уделяем его, вызывается размерный длит.
Если каким-то образом произошло преобразование к void, указательно void или на класс, который не до конца определён,
Тогда называется безразмерный долит.
Если в 26 стандарте этого не станет, конечно, нам будет проще.
И, конечно же, нам нужен оператор функции молок.
Мы должны засечь использование этой функции.
Но, опять же, не нарушая действия молока.
Это удобно сделать, передав просто флаг линковщика.
Мы хотим сделать обертку для молок.
Здесь реал молок, судя по названию.
Это действительно молок, который стандартный.
И в WrapMalloc это наша функция обертка.
Перед чем вызвать настоящий молок, мы можем поместить туда свой код, такой же, как в InU.
И возникает все-таки вопрос, что же делать, когда монитор включен?
Наша первая мысль была, почему не использовать исключения для этого?
Давайте поместим там троу стадо Run Time Error с каким-то сообщением.
Хорошо, мы это сделали и это не сработало.
Потому что Run Time Error конструктор его вызывает operator new.
Оператор new опять же вызывает генируют исключение Run Time Error.
В общем, ситуация неприятная.
Почему так произошло?
И GCC и IR, реализация выглядит похоже.
Ну и действует он тоже аналогично.
Мы видим, что внутри
Типа коу-стринг.
Что это за коу-стринг?
Это обычный стринг.
Вернее, не совсем обычный.
Это коу-стринг из хидера коу-стринг.h.
Это так называемый классический копион-райт-стринг.
Рантамерож собирается именно с ней.
У этого объекта есть неприятная особенность, что она динамически выделяет память для любой строки, даже если там один символ.
Нам не пришлось ничего с этим делать, потому что, я думаю, что вы уже знаете, как работает механизм исключений.
И это просто вызови функции, одна из которых allocate exception резервирует место для исключения.
Резервирует она его тоже динамически через молок.
Для...
некоторых устройств допустимо перегрузить эти функции, переопределить их и использовать не динамическую память, а статическую какую-то, откуда-то.
И возьмем неважно.
В принципе, это допустимо.
Но для высоких уровней силы это уже не так допустимо и было коллегиальное решение.
убрать вообще исключение.
Это было тяжелое решение, но пришлось.
Мы добавляем флаг FNOW EXCEPTIONS, собираемся с ним.
То есть понятно, мы не можем обрабатывать исключение, но зато мы можем написать свою функцию исключения.
Мы просто вызываем функцию BSP EXCEPTION.
как изменится мейн при этом.
Все блоки обработки исключений уйдут, и мы можем назначить некий глобальный хендлер, глобальный обработчик исключений через SetException.
То есть такой же механизм как, например, Terminate и SetTerminate.
И в нём мы просто передаём лямду, которая переводит устройство в безопасный режим.
То есть действия абсолютно такой же.
Разницы почти не заметили.
Но не сразу, потому что есть важные замечания, что линковать это нужно с правильной версией стандартной библиотеки.
Если вы линкуете ее с полной версией, например, вы хотите вызвать пустой объект sd-функшн, неназначенный ничем, он вызывает trowBetEx function call.
Внутри него обычно бросается исключение, bad function call.
Как вы можете видеть,
снова появляется AllocateException.
Несмотря на то, что мы собираем с флагом FnullException.
Поэтому нужно линковать ее с правильной версией, где исключения тоже выключены.
И в этом случае мы получим просто вызов функции Abort, которую, если вы хотите изящно перевести ваше устройство в SaveState, то тоже нужно перехватить каким-то образом.
и так же, кто у нас попался в нашем мониторе.
Я думаю, всем известный тип, потому что в стандартной библиотеке Шаблонов много кто использует динамическое распиление памяти.
Это STD String, Vector, Unik Pointer, Shared Pointer и даже STD Function.
Что с ними делать?
Были какие мысли?
Заместить можно аналогом, написать собственную реализацию.
Эти два пункта очень трудоемкие.
Либо можно использовать аллокатор.
Поможет ли он?
Узнаем.
Какие замены предлагаются?
Есть банальные замены, как бы STD-вектор, заменить STD-эрей.
И во многих местах это реально сработало.
СТД стринг.
Заметьте на стринг view.
Слава богу у нас в проекте не так много работы со строчками.
То есть, в основном это что-то сохранить лок, например.
И тут вполне подходит СТД стринг view.
Потому что все строчки можно представить как массив в какой-то постоянной памяти.
И здесь они создаются хорошо.
И плюс есть еще какие-то функции работать со строчками, что тоже прекрасно.
Unique Pointer.
Тут сложный вопрос.
На что его заменить?
Писать своему творчину не хотели, но хорошо, что он очень слабо связан с динамической локацией.
Мы должны передать ему результат выполнения New либо в конструкторе, либо через функцию MyKinic.
Здесь вот вызывается оператор new, оператор delete вызывается функцией reset, но тоже есть нюанс, что он это делает через объект default delete, так называемый deliter, который передается ему в качестве шаблонного аргумента по умолчанию, второго шаблонного аргумента по умолчанию.
можно заменить тогда UnicPointer на UnicPointer с собственным deliter.
Любой объект можно использовать как deliter, если его можно вызвать и передать ему указатель на объект, который мы хотим удалить.
Например, если у нас есть какой-то статический полпамяти, в конструкторе мы передаем результат выполнения Locate и передаем вторым аргументам лямду, которое делает Delocate.
Лямда приводится к указателю на функцию.
Можно пойти более честным путем, написать свой дилетер.
Тогда его можно использовать даже не с статическими полными памяти.
У нас есть полпамяти.
Передаем его в конструкторе для Deleter.
Определяем оператор скобочки.
Делаем диалокацию.
Создаем от объекта, передаем его в Unique Pointer.
В принципе, по такой схеме можно работать успешно.
И кто же еще там был?
Estada Function.
Она очень похожа на STD String.
Если мы какую-то небольшую строчку создаем через STD String, у нас не будет динамика локации, ему хватит внутреннего буфера.
в SDFunction точно так же.
То есть какие-то небольшие лямды он может захватывать так.
Но довольно большие лямды, не хватит памяти, он вызовет конечно динамическую локацию.
Как, например, здесь?
Почему здесь это сработало?
Чем определяется внутренний буфер SDFunction?
Размер его определяется через объединение ноу-копи-тайпс.
Самый жирный объект здесь указатель на метод класса.
По размеру это как два указателя обычных.
И поэтому все лямбды меньше этого размера, мы можем разместить его с этот функшен безболезненно.
но наша лямда с того слайда ее можно развернуть так мы захватываем там аж три переменных типа n64 это довольно много это никак не меньше будет 32 бит понятно байт тут не хватит никакого внутреннего буфера поэтому понятно почему там был вызов не забавный факт что
В стандарте существовал конструктор для CDFunction, который принимали как параметр Allocator.
Но по признанию самой GCC он никогда не работал корректно, эти конструкторы.
Параметр либо игнорировался, либо у других реализаций не GCC, были проблемы с переносом его прикопирования.
И закономерный итог в 17 стандарте, он уходит.
Мы больше не можем использовать локаторы.
И в IR есть тоже особенность реализации CD function, она там немного другая.
И у нее есть такой интересный конструктор.
Когда мы передаем туда лямду, хотим передать, мы просто задаем так таким образом.
То есть есть некий шаблонный аргумент.
Фанк — это лямда, FX — это тип лямды.
Выглядит хорошо, но если мы попробуем вызвать нашу метафункцию из Constructable, то есть спросить, можно ли создать std-function от типа int.
Он скажет, почему нет?
Можно.
Ну что, здесь он проверяет как бы интерфейс.
Мы можем передать туда инд, можем.
То, что это не соберется, потом его не интересует.
Спасло бы нас использование сфина здесь.
Это так вот можно исправить, проверить, что это вызываемый объект именно, а не просто какой-то тип.
Но это, по-моему, до сих пор я не исправил, что
толкает нас в следующий авантюру, что давайте допишем свою STD Function.
Мы проназировали свои требования, мы хотим хранить там не только указатель на метод класса, но и указатель на сам класс.
Почему нет?
Это для нас очень частый случай, это удобно.
Потом мы не хотим использовать динамическую локацию, поэтому все, что
не влезает в наш буфер.
Мы просто не будем компилировать.
И мы не будем бросать исключение.
Если у нас есть какое-то пустое функшн, если мы его вызываем, возвращается просто аргумент значений по умолчанию.
И решили мы начать со внутреннего буфера для STD Function.
Это такой универсальный буфер, где мы можем хранить любой объект.
В данном случае нас интересует функциональный объект.
Видите, как раз размер буфера ограничен.
тремя соизофами от указателя.
И также мы можем получить доступ к оригинальному объекту через парметрическую Get.
Сама Function.
Прощенная версия.
Не такая сложная.
Главный действующий лиц здесь.
Фанктар.
Это внутренний буфер как раз есть, где мы храним функциональные объекты любого типа.
и некая vtable.ptr указатель на виртуальную таблицу.
Но она не настоящая.
Мы ее делаем вручную.
Потому что как работать с функциальным объектом, тип которого мы знаем только в конструкторе.
Конечно, создать несколько функций для работы с ним там же.
Для работы нам потребуется двок, чтобы его вызвать.
Дистракт и клон, клон, чтобы скопировать.
Это концепция называется fakeVTable и здесь вот мы ее применяем довольно успешно.
Есть у нас помогательный класс handler.
У него есть шаблонные параметры.
возвращаемый тип аргументы и еще тип нашего функционального объекта, поэтому это как бы уникальный класс.
evitable, который он генирует тоже уникально, нам в конструкторе остается только запомнить указатель на нее.
int переносит переданный в конструктор функциональный объект в наш внутренний буфер.
Как выглядит этот класс?
Не особо сложно тоже.
Главное, что есть тут ограничения.
Вот здесь, если мы попробуем передать что-то большое, ограничится статик ассертом.
Keep int, placement new, мы переносим функциональный объект внутрь переданного фактора.
И сама таблица.
Сама таблица будет состоять из указателей на статические функции.
Здесь мы видим, что как раз мы берём переданный внутренний буфер.
Через Get получаем доступ к оригинальному объекту и вызываем его с передными аргументами.
И всё, как бы до этого.
Оператор, можно перед... Оператор скобочки можно использовать вот так.
То есть, вызываем из Virtual Table не настоящий функции наволок.
Передаем его внутренний буфер и аргументы.
как бы подход неплохо работает для нас, потому что мы не ставили целью создавать какой-то универсальный инструмент, переиграть STD Function.
Нет, он работает только на каких-то особых случаях, которые нужны нам.
Аллокаторы.
Можно ли их использовать?
Можно попробовать.
И первое, что приходит в голову.
Это полиморфный аллокатор.
Довольно просто, я думаю, понять его концепцию.
То есть мы наследуемся от memory resource и создаем свой источник памяти.
Допустим, у нас есть некий буфер, который мы выделяем на статике и из него как бы черпаем память уже по запросу.
Допустим, да, у нас есть некий алгоритм для этого.
Тогда мы просто перепределяем функции doLocate и можем использовать новый источник памяти.
То есть объявляем наш статус Decalocator, мы можем использовать его глобально через celldefault resource.
И тогда для каждого объекта, который мы создаем из пространства имен std.pmr,
Он будет использовать этот локатор, который дефолтный.
Лучше использовать его локально.
Неглобально.
То есть создать объект типа полиморфика локатор и передавать его в конструктор.
Так как бы за ними больше контроля.
Точно так же можно использовать эстодивектор.
То есть из того же пространства имен мы создаем sdmr-вектор, и соответствующий алкатер мы тоже ставим у default, либо используем по лимошке колокатер-класс.
Тут никакой разницы нет.
И даже SharedPointer мы можем создавать его через функцию AllocateShared с абсолютно тем же самым источником памяти в статике алкатер.
Единственный минус пульморфного локатора — он не поддерживается ярым.
Я говорил, что это 17-й стандарты с некоторыми изъятиями.
Вот изъятие.
На помощь нам приходит классический локатор.
Или кто-то говорит «Лягость» — а локатор.
Я слышал так и так.
Идея вся та же самая.
У нас есть память на статике.
И эту память мы используем для всех объектов статика локатор с такими параметрами.
Обязательно перепределяем value type функцию rebind и конечно же allocate-delocate самые главные функции.
И тут нет никакой разницы, используем ли мы племорф на локатору, классический локатор, ну почти никакой разницы.
То есть мы создаем свой тип, переопределяем базик String с нашим статическом локатором.
Также мы можем это сделать глобально, локально передавать его в конструктор.
То же самое с вектором.
И почти так, но нужно доработать напильником.
Наш статик аллокатор, чтобы можно было его использовать для функции AllocateShared.
Там не все так просто, там придется повозиться функции Rebind.
Потому что не совпадают то, что он хранит, чтобы создать его полноценно на статической памяти.
Нужно чуть больше места для еще каких-то служебных структур.
Но под вечер такие слайды показывать нельзя, они высветут вас ума.
И заключение, можно сказать, что мы справились со своей задачей, это вы видите таблицу того, что можно использовать, что можно использовать с доработками и что никак нельзя использовать.
Ну, таких объектов мало, таких типов мало.
Это, в принципе, привычный C++ с некоторыми изъятиями.
И в некоторых местах мы предпочитаем понятный код, очевидный код, коду, которому удобно писать.
Но, как показывает практика,
Несколько последних недель, что збоя не любит никто, особенно массовые збоя.
И все хотят надежности и предсказуемости.
А это и есть цель нашего стандарта в безопасности.
Так что я думаю, что каждый может найти в себе интересную практику.
Всё, спасибо.
Спасибо за доклад.
Давайте вопросы.
Сразу две руки.
Нам нужны два микрофона.
Вопрос сразу такой.
Если мы все равно используем локаторы, которые мы алацировали на стеке, то почему бы нам просто не запретить молоку делать им мапы?
Потому что по дефолту молок берет свою начальную кучу с куска, который нам уже выделен.
Почему бы просто не запретить ему использовать мапы и так победить?
Хороший вопрос.
Потому что мы не хотим нарушать работы именно молока.
Мы отсекаем вообще его использование, а не лезем внутрь молока.
Как будто это просто флаг в эпизоде всем, на который вы крутитесь, если я правильно помню.
А откуда тогда локация?
А локации.
Так.
Но это зависит, даже если моложе по-разному бывает.
И в Айаре тоже, как бы, в GCC, она может по-разному действовать.
Там в Айаре вообще он всё своё привносит.
Это его такой путь.
Так что легче контролировать использование молока, чем что-то с ним делать ещё дополнительно.
Окей, да, понятно, спасибо.
В ту же стезию вопрос.
Я почему-то всегда считал, что вот барметр запускаешь там или даже компилируешь туда код.
Тебе говорят, ой, ну нет такого символа как молок, фри.
То есть ну как бы сишный либо просто нет.
Получается на самом деле она есть на имбеда в устройствах.
И там как-то молок работает все-таки, да?
Абсолютно верно.
И я с ним все-таки бороться надо не путем просто не вызывать в коде нигде, а вот получается мониторами, как у вас.
И вот еще хотел добавить, есть такая библиотека у любителей Дел Тигры, я СТЛ называется.
И вот там достаточно много всяких классов, которые делают то же самое, что эстэдэшные, но без локаций.
Там фикст-фанкшн, фикст-вектор, фикст-сет, фикст-мап и так далее.
Мне кажется, вот ее взять вам бы вполне пригодилось.
Это отличная идея.
Мы нашли кучу библиотек, которые делают примерно то же самое.
Но в чем проблема?
Там такой цикл разработки, что даже какой-то не с очень сильной командой, вы должны все равно прийти к безопасному коду.
И одно из правил — это очень жесткая контроль со стороны так называемого отдела одобрения, опровол депадмент.
И если вы не доказали, что это безопасная библиотека, которую вы хотите притащить всегда в проект, тогда вы не можете ее использовать.
А доказать это порой сложнее, чем просто реализовать это функциум свой.
Ну, скопипастить можно было бы.
Ну ладно, это понятно, что за библиотеком тяжело.
Спасибо.
Так, да.
Данила, здравствуйте.
Спасибо за ваш доклад.
У меня такой вопрос.
Вот у Гореля о безопасности.
Там вот проверяли численные диапазоны.
Учислил, то есть можно их использовать или нет.
А вот для чисел с плавающей точкой.
там вот нота-намбор превышает там какие-нибудь инфинити появляющиеся во время каких-нибудь тригонометрических вычислений.
То есть вы такими делами не занимались?
Там есть свои правила на этот счет.
В частности нужно реализовать как бы сравнение флот.
Ну нельзя их просто сравнивать там стандартными.
Вот, например, результат Аркосинус, там, если он буквально в вычисление чуть превысит единицу, да, с нам уже нота-намбор и, собственно, это уже, ну, дальше программа уже может просто валиться, как говорится, не работать.
Хотя математика логика верна, а результат из-за неточности вычисления можешь привести к тому, что программа перестанет корректно работать.
Как решаются эти проблемы именно для обеспечения такой степени надежности?
Я говорю, там есть свои функции, которые вы используете с типом флоут.
Я уже точно не помню, но вот сравнение, я помню, что это точно было свое написано, что нужно элипс задавать, какой-то стандартный действие, потому что есть сложности с ноты number, совсем остальным, и должно как-то проверяться.
К сожалению, точно не помню, как это делалось.
Не помните, то есть у нас статик или врантайме это как проверяется?
Точно врантайме это была проверка.
Каким именно путем точно не скажу.
Но там есть прямо определенные безопасности стандарты связанной с лод и с лоущей точкой.
Следующий вопрос задам из чата и он, наверное, будет последним.
Спрашивают, правда ли, что все проверки на использование динамической памяти проводятся только в рантайме?
Получается, так и в таком случае тестовое покрытие должно покрывать все строжьи кода.
Так ли это?
Независимо от того, где проверяются динамические локации, молоки и оператор Нью, тестовое покрытие все равно должно быть 100%.
За счет чего гарантируется, что динамическое выделение памяти?
Естественно, да.
И это тоже гарантирует нам это.
Потому что это правило, да.
Провел департмент.
Если оно видит, что у вас тестовый покрытие 60%, нет.
Такой код не годится.
Каждый файл, новый созданный, каждый класс должен иметь юнитесты.
Это минимум.
Ну и, конечно, все брончи там тоже нужно пройти.
Понятно.
И еще дополнение к этому же вопросу.
Как вы доказываете отсутствие рекурсии в C++-коде?
Это доказывает статическое анализация.
Потому что если где-то есть неявная рекурсия, он пишет об этом предупреждение.
Хорошо, то есть вы пишете код без рекурсии, а если она все-таки появляется, то вам статижный анализатор помогает ее выветь.
Если нету сообщения, значит, наверное, все в порядке.
Тогда поблагодарим нашего спикера аплодисментами.
Спасибо.