Перейти к содержанию

Куча (динамическая память)

Память, которую программа запрашивает во время работы через malloc или new, берётся из кучи. В микроконтроллере куча делит с стеком одну и ту же свободную часть ОЗУ и растёт ему навстречу, поэтому ошибки с ней приводят к порче данных без всякого предупреждения.

Обновлено Редакция GAW

Куча (heap, динамическая память) — область ОЗУ, из которой программа во время работы получает память вызовами malloc, calloc или оператором new и возвращает через free или delete. В отличие от переменных в секциях .data и .bss, размер которых известен при компоновке, размер кучи меняется по ходу работы.

Где она в ОЗУ

avr-libc описывает типичную раскладку: в начале ОЗУ .data, за ней .bss, куча — сразу после конца .bss, а стек начинается с вершины ОЗУ и растёт вниз. Куча и стек растут навстречу друг другу, и аппаратной защиты между ними нет. Шаблон ST для STM32F103 устроен так же: функция _sbrk выдаёт память, начиная с символа end, и не поднимается выше текущего указателя стека.

avr-libcSTM32F103: шаблон ST и syscalls.c STM32CubeF1
Начало кучи__heap_start, после .bssend, после .bss
Пределстек минус __malloc_margin (32 байта)текущий указатель стека
Проверка при сборке—_Min_Heap_Size 0x200, _Min_Stack_Size 0x400

Почему кучу избегают

  • Фрагментация. После выделения и освобождения блоков разного размера свободная память дробится, и большой запрос не помещается ни в одну дыру, хотя суммарно места хватает.
  • Столкновение со стеком. Глубокая вложенность вызовов или прерываний может залезть в область кучи; avr-libc оставляет запас всего 32 байта на всё, что может вложиться в текущую функцию.
  • Накладные расходы. У avr-libc каждый блок несёт 2-байтовый заголовок с размером.

Поэтому в прошивках буферы обычно объявляют статически, а malloc вызывают разве что при старте.

Не путать кучу со стеком: стек хранит локальные переменные и адреса возврата и освобождается автоматически при выходе из функции, а блок кучи живёт, пока его не освободят. Как задать и проверить размеры кучи и стека — в статье «Скрипт компоновщика GNU ld», типы и распределение памяти в C — в статье «Язык C для микроконтроллеров».

Частые вопросы

Где находится куча в ОЗУ микроконтроллера?

Сразу после .bss, а стек начинается с вершины ОЗУ и растёт вниз — навстречу куче. Так описывает раскладку avr-libc, так же устроен шаблон ST для STM32F103. Аппаратной границы между ними нет: если куча и стек встретятся, они начнут портить друг друга.

Что такое фрагментация кучи?

После многих выделений и освобождений блоков разного размера свободная память оказывается разбита на мелкие «дыры». Суммарно места хватает, но ни одна дыра не вмещает новый запрос, и malloc возвращает NULL или куча растёт навстречу стеку. avr-libc прямо называет фрагментацию причиной столкновения кучи со стеком.

Сколько памяти под кучу в проекте STM32?

В шаблоне скрипта компоновщика ST — минимум 0x200 байт (512) под кучу и 0x400 (1024) под стек. Это не ограничение, а проверка: компоновщик выдаст ошибку, если после .bss не осталось хотя бы этого. Реально куча растёт, пока не упрётся в указатель стека — так написана функция _sbrk в примерах STM32CubeF1; в других шаблонах предел может быть другим.

Источники

  1. AVR-LibC 2.3.2: Memory Areas and Using malloc() (malloc.dox), util/ram-usage.h — стандартная раскладка ОЗУ: .data, затем .bss, куча после конца .bss, стек сверху вниз; аппаратной защиты нет; куча и стек могут столкнуться, в том числе из-за фрагментации; malloc не поднимается выше текущего стека минус __malloc_margin, по умолчанию 32 байта; у каждого блока 2-байтовый заголовок с размером; _get_ram_unused несовместима с malloc
  2. STMicroelectronics. cmsis-device-f1, коммит c8e9a4a: Source/Templates/gcc/linker/STM32F103XB_FLASH.ld — _Min_Heap_Size = 0x200, _Min_Stack_Size = 0x400; секция ._user_heap_stack проверяет, что после .bss остаётся место под кучу и стек
  3. STMicroelectronics. STM32CubeF1, коммит c7656f1: syscalls.c примеров — _sbrk: куча начинается с символа end и не поднимается выше текущего указателя стека

Нашли ошибку?