Справочник по Ada (Ada 2022)
13.11 Управление памятью
Каждый тип доступа к объекту имеет связанный пул памяти. Память, выделенная аллокатором, берется из пула; экземпляры Unchecked_Deallocation возвращают память в пул. Несколько типов доступа могут использовать один и тот же пул.
Пул памяти — это переменная типа, входящего в иерархию, укоренённую в Root_Storage_Pool, который является абстрактным ограниченным управляемым типом. По умолчанию реализация выбирает стандартный пул памяти для каждого типа доступа к объекту. Пользователь может определять новые типы пулов и может переопределить выбор пула для типа доступа к объекту, указав Storage_Pool для типа.
Правила допустимости
Если для данного типа доступа указан Storage_Pool, то Storage_Size для него не должен быть указан.
Статическая семантика
Существует определённый библиотечный пакет языка:
with Ada.Finalization;
with System.Storage_Elements;
package System.Storage_Pools
with Pure, Nonblocking => False is
with System.Storage_Elements;
package System.Storage_Pools
with Pure, Nonblocking => False is
type Root_Storage_Pool is
abstract new Ada.Finalization.Limited_Controlled with private
with Preelaborable_Initialization;
abstract new Ada.Finalization.Limited_Controlled with private
with Preelaborable_Initialization;
procedure Allocate(
Pool : in out Root_Storage_Pool;
Storage_Address : out Address;
Size_In_Storage_Elements : in Storage_Elements.Storage_Count;
Alignment : in Storage_Elements.Storage_Count) is abstract;
Pool : in out Root_Storage_Pool;
Storage_Address : out Address;
Size_In_Storage_Elements : in Storage_Elements.Storage_Count;
Alignment : in Storage_Elements.Storage_Count) is abstract;
procedure Deallocate(
Pool : in out Root_Storage_Pool;
Storage_Address : in Address;
Size_In_Storage_Elements : in Storage_Elements.Storage_Count;
Alignment : in Storage_Elements.Storage_Count) is abstract;
Pool : in out Root_Storage_Pool;
Storage_Address : in Address;
Size_In_Storage_Elements : in Storage_Elements.Storage_Count;
Alignment : in Storage_Elements.Storage_Count) is abstract;
function Storage_Size(Pool : Root_Storage_Pool)
return Storage_Elements.Storage_Count is abstract;
return Storage_Elements.Storage_Count is abstract;
private
... -- не указано языком
end System.Storage_Pools;
... -- не указано языком
end System.Storage_Pools;
Тип пула памяти (или тип пула) — это потомок Root_Storage_Pool. Элементы пула памяти — это объекты, выделенные в пуле аллокаторами.
Для каждого подтипа доступа к объекту S определены следующие атрибуты представления:
S'Storage_Pool
Обозначает пул памяти типа S. Тип этого атрибута — Root_Storage_Pool'Class.
S'Storage_Size
Возвращает результат вызова Storage_Size(S'Storage_Pool), который предназначен для измерения количества элементов памяти, зарезервированных для пула. Тип этого атрибута — universal_integer.
Storage_Size или Storage_Pool могут быть указаны для непроизводного типа доступа к объекту с помощью пункта определения атрибута; имя, указанное в пункте Storage_Pool, должно обозначать переменную. Если номинальный подтип имени, указанного для Storage_Pool, является неблокирующим (см. 9.5), то примитивные подпрограммы Allocate, Deallocate и Storage_Size этого типа должны быть неблокирующими. Кроме того, если пул поддерживает подпулы (см. 13.11.4), то примитивные подпрограммы Default_Subpool_for_Pool, Allocate_From_Subpool и Deallocate_Subpool должны быть неблокирующими.
Аллокатор типа T, не поддерживающего подпулы, выделяет память из пула памяти типа T. Если пул памяти — это определяемый пользователем объект, то память выделяется путём вызова Allocate, как описано ниже. Аллокаторы для типов, поддерживающих подпулы, описаны в 13.11.4.
Если Storage_Pool не указан для типа, определённого с помощью определения доступа к объекту, то реализация выбирает стандартный пул памяти для него способом, определённым реализацией. В этом случае исключение Storage_Error генерируется аллокатором, если памяти недостаточно. Определяется реализацией, предоставляет ли она имена стандартного типа пула(ов) для доступа пользователю.
Тип(ы) стандартного пула(ов) и примитивные подпрограммы Allocate, Deallocate и Storage_Size для стандартного пула(ов) являются неблокирующими. Одновременные вызовы этих подпрограмм не конфликтуют друг с другом (см. 9.10), когда они применяются к стандартным пулам памяти.
Если Storage_Size указан для типа доступа T, используется определённый реализацией пул P для типа. Storage_Size P не меньше запрашиваемого значения, а память для P возвращается, когда родительский блок, содержащий объявление типа доступа, покинут. Если реализация не может удовлетворить запрос, Storage_Error генерируется в точке заморозки типа T. Пул памяти P используется только для аллокаторов, возвращающих тип T или другие типы доступа, указанные для использования T'Storage_Pool. Storage_Error генерируется аллокатором, возвращающим такой тип, если память пула P исчерпана (дополнительная память не выделяется). Тип P и примитивные подпрограммы Allocate, Deallocate и Storage_Size для P являются неблокирующими.
Если не указаны ни Storage_Pool, ни Storage_Size, то значение Storage_Size определяется реализацией.
Если Storage_Pool указан для типа доступа, то используется указанный пул.
Эффект вызова Allocate и Deallocate для стандартного пула памяти напрямую (а не неявно через аллокатор или экземпляр Unchecked_Deallocation) не определён.
Ошибочное выполнение
Если Storage_Pool указан для типа доступа, то, если Allocate может удовлетворить запрос, он должен выделить непрерывный блок памяти и вернуть адрес первого элемента памяти в Storage_Address. Блок должен содержать Size_In_Storage_Elements элементов памяти и должен быть выровнен согласно Alignment. Выделенная память не должна использоваться для других целей, пока элемент пула существует. Если запрос не может быть удовлетворён, Allocate должен распространить исключение (например, Storage_Error). Если Allocate ведёт себя каким-либо иным образом, то выполнение программы является ошибочным.
Требования к реализации
Процедура Allocate объекта пользовательского пула памяти P может вызываться реализацией только для выделения памяти для типа T, пул которого — P, только в следующих точках:
- Во время выполнения аллокатора типа T;
- Во время выполнения оператора return для функции, результат которой создаётся на месте в результате аллокатора типа T;
- Во время выполнения операции присваивания с целевым объектом выделенного типа T с неявным дискриминирующим подтипом с значениями по умолчанию.
Для каждого вызова Allocate, описанного выше, P (эквивалентно T'Storage_Pool) передаётся в качестве параметра Pool. Параметр Size_In_Storage_Elements указывает количество выделяемых элементов памяти и не превышает D'Max_Size_In_Storage_Elements, где D — указанный подтип T. Параметр Alignment — это ненулевое целое кратное D'Alignment, если D — определённый тип, а в противном случае — ненулевое целое кратное выравниванию определённого типа, идентифицируемого меткой создаваемого объекта; если такого значения нет, оно не определено. Параметр Alignment не превышает D'Max_Alignment_For_Allocation. Возвращаемое значение в параметре Storage_Address используется в качестве адреса выделенной памяти, которая представляет собой непрерывный блок памяти из Size_In_Storage_Elements элементов памяти. Любое исключение, распространяемое Allocate, распространяется конструкцией, содержащей вызов.
Количество вызовов Allocate, используемых для реализации аллокатора для любого конкретного типа, не определено. Количество вызовов Deallocate, используемых для реализации экземпляра Unchecked_Deallocation (см. 13.11.2) для любого конкретного объекта, равно количеству вызовов Allocate для этого объекта.
Процедура Deallocate объекта пользовательского пула памяти P может вызываться реализацией для освобождения памяти типа T, пул которого — P, только в тех местах, где разрешён вызов Allocate для P, во время выполнения экземпляра Unchecked_Deallocation для T или как часть финализации коллекции T. Для такого вызова Deallocate P (эквивалентно T'Storage_Pool) передаётся в качестве параметра Pool. Значение параметра Storage_Address для вызова Deallocate — это значение, возвращённое в параметре Storage_Address соответствующего успешного вызова Allocate. Значения параметров Size_In_Storage_Elements и Alignment — те же значения, что и в соответствующем вызове Allocate. Любое исключение, распространяемое Deallocate, распространяется конструкцией, содержащей вызов.
Требования к документации
Реализация должна документировать набор значений, которые должна принимать процедура пользователя Allocate для параметра Alignment. Реализация должна документировать, как выбирается стандартный пул памяти и как память выделяется стандартными пулами памяти.
Рекомендации по реализации
Реализация должна документировать любые случаи, в которых она динамически выделяет кучную память для цели, отличной от оценки аллокатора.
По умолчанию (предоставленный реализацией) пул памяти для типа доступа к константе не должен иметь накладных расходов для поддержки освобождения отдельных объектов.
Пул памяти, используемый для аллокатора анонимного типа доступа, должен определяться следующим образом:
- Если аллокатор определяет корасширение (см. 3.10.2) объекта, создаваемого внешним аллокатором, то пул памяти, используемый для внешнего аллокатора, также должен использоваться для корасширения;
- Для других дискриминантов доступа и параметров доступа пул памяти должен создаваться в момент аллокатора и возвращаться, когда выделенный объект становится недоступным;
- Если аллокатор определяет результат функции с результатом доступа, пул памяти определяется так, как будто аллокатор был на месте вызова функции. Если вызов является операндом преобразования типа, пулом памяти является пул целевого типа доступа преобразования. Если сам вызов определяет результат функции с результатом доступа, это правило применяется рекурсивно;
- В противном случае, пул памяти по умолчанию должен быть создан в момент, когда анонимный тип доступа уточняется; такой пул памяти может не иметь механизма для освобождения отдельных объектов.
ПРИМЕЧАНИЕ 1 Пользовательский тип пула памяти может быть получен путем расширения типа Root_Storage_Pool и переопределения примитивных подпрограмм Allocate, Deallocate и Storage_Size. Пользовательский пул памяти можно получить, объявив объект типа расширения. Пользователь может переопределить Initialize и Finalize, если существует необходимость в нетривиальной инициализации и завершении для пользовательского типа пула. Например, Finalize может возвращать блоки памяти, выделенные отдельно от объекта самого пула.
ПРИМЕЧАНИЕ 2 Автор пользовательских процедур выделения и освобождения и пользователи аллокаторов для связанного типа доступа несут ответственность за обработку любых взаимодействий с задачами. В частности:
Если аллокаторы используются в разных задачах, они требуют взаимного исключения.
Если они используются внутри защищенных объектов, они не могут блокироваться.
Если они используются обработчиками прерываний (см. C.3, «Поддержка прерываний»), механизм взаимного исключения должен работать правильно в этом контексте.
ПРИМЕЧАНИЕ 3 Примитивы Allocate, Deallocate и Storage_Size объявлены абстрактными (см. 3.9.3), и поэтому они должны быть переопределены при объявлении нового (не абстрактного) типа пула памяти.
Примеры
Чтобы связать тип доступа с объектом пула памяти, пользователь сначала объявляет объект пула некоторого типа, производного от Root_Storage_Pool. Затем пользователь определяет его атрибут Storage_Pool следующим образом:
Pool_Object : Some_Storage_Pool_Type;
type T is access Designated;
for T'Storage_Pool use Pool_Object;
for T'Storage_Pool use Pool_Object;
Другой тип доступа может быть добавлен в существующий пул памяти с помощью:
for T2'Storage_Pool use T'Storage_Pool;
Семантика этого определяется реализацией для стандартного пула памяти.
Как обычно, производный от Root_Storage_Pool может определять дополнительные операции. Например, рассмотрите тип Mark_Release_Pool_Type, определенный в 13.11.6, который имеет две дополнительные операции, Mark и Release. Следующее является возможным использованием:
type Mark_Release_Pool_Type
(Pool_Size : Storage_Elements.Storage_Count)
is new Subpools.Root_Storage_Pool_With_Subpools with private;
-- Как определено в пакете MR_Pool, см. 13.11.6
(Pool_Size : Storage_Elements.Storage_Count)
is new Subpools.Root_Storage_Pool_With_Subpools with private;
-- Как определено в пакете MR_Pool, см. 13.11.6
...
Our_Pool : Mark_Release_Pool_Type (Pool_Size => 2000);
My_Mark : Subpool_Handle; -- Как объявлено в 13.11.6
My_Mark : Subpool_Handle; -- Как объявлено в 13.11.6
type Acc is access ...;
for Acc'Storage_Pool use Our_Pool;
...
for Acc'Storage_Pool use Our_Pool;
...
My_Mark := Mark(Our_Pool);
... -- Выделить объекты с помощью «new (My_Mark) Designated(...)».
Release(My_Mark); -- Завершить объекты и вернуть память.
... -- Выделить объекты с помощью «new (My_Mark) Designated(...)».
Release(My_Mark); -- Завершить объекты и вернуть память.