Как известно, в языке Си для динамического выделения и освобождения памяти на куче используются функции malloc()
и free()
. В С++ для этих целей предусмотрены операторы new
и delete
. Когда оператор new
не может удовлетворить запрос на выделение памяти, он возбуждает исключение std::bad_alloc
, сигнализируя тем самым об ошибке. В C++ для микроконтроллеров механизм обработки исключений обычно отключён для экономии ресурсов, однако согласно спецификации С++, прежде чем возбудить исключение std::bad_alloc
, оператор new
вызывает специальную функцию-обработчик. Эту функцию можно переопределять std::set_new_handler
, что позволяет хоть как-то контролировать / фиксировать ошибки выделения памяти на куче в процессе выполнения программы. Но это только верхушка айсберга, а ниже описаны более эффективные и надёжные приёмы. Чтобы было нескучно, в качестве тестового материала взята игра «Змейка».
-
операторы
new
иdelete
в С++ можно перегружать, а в контексте микроконтроллеров может оказаться весьма полезным вообще сломать компиляцию при попытке динамического выделения памяти на куче no-new.h. Для того, чтобы проверка происходила во всех исходных файлах проекта автоматически в gcc подобных компиляторах достаточно просто указать в командной строке опцию-include no-new.h
, в случае со «Змейкой» используется IDE Code Composer Studio, там есть настройка --preinclude. Что касается наследия Си — функций динамического выделения памятиmalloc
,calloc
,realloc
— то красиво отцепить их увы не получится. У gcc компиляторов есть волшебный флаг компоновщика-Wl,--wrap=malloc
, который даёт возможность ставить обёртки на функции. Как это сделать на примере покажу чуть позже -
«Змейка» кушает и растёт, т.е. представляет из себя динамический массив. Для STL контейнеров в С++ можно задавать альтернативные аллокаторы памяти. В большинстве случаев разработчик знает, сколько элементов контейнера может потребоваться для его алгоритма и тогда хорошей идеей выглядит аллокатор, использующий стек вместо кучи. Для древнего C++98 найти рабочий аллокатор затруднительно, поэтому для игры «Змейка» был взят этот контейнер, также использующий стек вместо кучи
-
существует особая форма оператора
new
, называемая Placement new. Данный оператор не выделяет память, а получает своим аргументом адрес на уже выделенную каким-либо образом память. Например память под объект «Змейка» выделяется в стеке на этапе компиляции, а размещение (инициализация) объекта в ней путём вызова конструктора происходит в процессе выполнения программы, таким образом можно пересоздавать «Змейку» при перезапуске игры
Под капотом «Змейки» QP/C++ и соответствущая диаграмма состояний UML:
На картинке иерархический конечный автомат Play где отсчитываются временные интервалы для движения «Змейки» и обрабатывается нажатие от кнопок. Если метод eat_or_move()
вернёт false
переход в состояние GameOver где отображается количество набранных очков. При нажатии любой кнопки снова Play.
«Змейка» представляет из себя динамический массив сегментов (квадратиков), для её передвижения в заданном направлении достаточно добавить новый сегмент головы в один конец массива и удалить сегмент хвоста — последний элемент с другого конца. На экране аналогично достаточно очистить хвост и дорисовать голову. При поедании пищи добавляется сегмент головы, а сегмент хвоста не удаляется.
Далее паттерн «Наблюдатель».