Здравствуйте, я Данил Демидов.
Работаю в компании «Ауриго».
Занимаюсь страймой системами, в основном на 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.
Там происходит короткая инцелизация.
Мы выставляем нужные значения нашим объектам и уходим в бесконечный цикл.
Там он пробегается по таскам, на самом деле это функции, это не таски.
И подает дисп слип, чтобы сэкономить немного энергии.
Это такая своеобразная кооперативная многозадачность.
И как это выглядит в коде?
У нас есть статический объект монитора.
И в блоке 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.
Потому что все строчки можно представить как массив в какой-то постоянной памяти.
И здесь они создаются хорошо.
И плюс есть еще какие-то функции работать со строчками, что тоже прекрасно.
Писать своему творчину не хотели, но хорошо, что он очень слабо связан с динамической локацией.
Мы должны передать ему результат выполнения New либо в конструкторе, либо через функцию MyKinic.
Здесь вот вызывается оператор new, оператор delete вызывается функцией reset, но тоже есть нюанс, что он это делает через объект default delete, так называемый deliter, который передается ему в качестве шаблонного аргумента по умолчанию, второго шаблонного аргумента по умолчанию.
можно заменить тогда UnicPointer на UnicPointer с собственным deliter.
Любой объект можно использовать как deliter, если его можно вызвать и передать ему указатель на объект, который мы хотим удалить.
Например, если у нас есть какой-то статический полпамяти, в конструкторе мы передаем результат выполнения Locate и передаем вторым аргументам лямду, которое делает Delocate.
Лямда приводится к указателю на функцию.
Можно пойти более честным путем, написать свой дилетер.
Тогда его можно использовать даже не с статическими полными памяти.
Передаем его в конструкторе для Deleter.
Определяем оператор скобочки.
Создаем от объекта, передаем его в Unique Pointer.
В принципе, по такой схеме можно работать успешно.
Она очень похожа на 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.
Главный действующий лиц здесь.
Это внутренний буфер как раз есть, где мы храним функциональные объекты любого типа.
и некая 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++-коде?
Это доказывает статическое анализация.
Потому что если где-то есть неявная рекурсия, он пишет об этом предупреждение.
Хорошо, то есть вы пишете код без рекурсии, а если она все-таки появляется, то вам статижный анализатор помогает ее выветь.
Если нету сообщения, значит, наверное, все в порядке.
Тогда поблагодарим нашего спикера аплодисментами.