Справочник по Ada 2012
13.11 Управление памятью
Каждый тип доступа к объекту имеет связанный пул памяти. Память, выделенная аллокатором, берётся из пула; экземпляры Unchecked_Deallocation возвращают память в пул. Несколько типов доступа могут использовать один и тот же пул.
Пул памяти — это переменная типа, корневым классом которого является Root_Storage_Pool, являющийся абстрактным ограниченным управляемым типом. По умолчанию реализация выбирает стандартный пул памяти для каждого типа доступа к объекту. Пользователь может определять новые типы пулов и может переопределять выбор пула для типа доступа к объекту, указав Storage_Pool для типа.
Правила допустимости
Если Storage_Pool указан для данного типа доступа, то Storage_Size для него указывать нельзя.
Статическая семантика
Существует следующая определяемая языком библиотечная пакет:
with Ada.Finalization;
with System.Storage_Elements;
package System.Storage_Pools is
pragma Preelaborate(System.Storage_Pools);
with System.Storage_Elements;
package System.Storage_Pools is
pragma Preelaborate(System.Storage_Pools);
type Root_Storage_Pool is
abstract new Ada.Finalization.Limited_Controlled with private;
pragma Preelaborable_Initialization(Root_Storage_Pool);
abstract new Ada.Finalization.Limited_Controlled with private;
pragma Preelaborable_Initialization(Root_Storage_Pool);
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 должно обозначать переменную.
Аллокатор типа T, не поддерживающий подпулы, выделяет память из пула памяти T. Если пул памяти — это пользовательский объект, то память выделяется путём вызова Allocate, как описано ниже. Аллокаторы для типов, поддерживающих подпулы, описаны в 13.11.4.
Если Storage_Pool не указан для типа, определённого определением_доступа_к_объекту, то реализация выбирает стандартный пул памяти для него способом, определённым реализацией. В этом случае исключение Storage_Error генерируется аллокатором, если памяти недостаточно. Реализуется ли доступ к именам стандартных типов пула, определяется реализацией.
Если Storage_Size указан для типа доступа T, используется определённый реализацией пул P для типа. Storage_Size P не меньше запрошенного, а память для P освобождается, когда мастер, содержащий объявление типа доступа, покидает область видимости. Если реализация не может удовлетворить запрос, Storage_Error генерируется в момент заморозки типа T. Пул памяти P используется только для аллокаторов, возвращающих тип T или другие типы доступа, указанные для использования T'Storage_Pool. Storage_Error генерируется аллокатором, возвращающим такой тип, если исчерпан объём памяти 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;
- Во время выполнения оператора возврата функции, результат которой строится на месте в результате аллокатора типа 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) объекта, создаваемого внешним аллокатором, то для корасширения следует использовать пул памяти, используемый для внешнего аллокатоpa;
- Для других дискриминантов доступа и параметров доступа пул памяти должен быть создан в момент работы аллокатора и высвобожден, когда выделенный объект становится недоступным;
- Если аллокатор определяет результат функции с результатом доступа, пул памяти определяется так, как если бы аллокатор был на месте вызова функции. Если вызов является операндом преобразования типа, пулом памяти является пул типа доступа назначения преобразования. Если вызов сам определяет результат функции с результатом доступа, это правило применяется рекурсивно;
- В противном случае, по умолчанию, пул памяти должен быть создан в момент, когда уточняется анонимный тип доступа; такой пул памяти не обязан поддерживать выделение отдельных объектов.
ПРИМЕЧАНИЯ
27 Пользовательский тип пула памяти можно получить, расширив тип Root_Storage_Pool и переопределив примитивные подпрограммы Allocate, Deallocate и Storage_Size. Пользовательский пул памяти затем можно получить, объявив объект типа расширения. Пользователь может переопределить Initialize и Finalize, если требуется нетривиальная инициализация и завершение работы для пользовательского типа пула. Например, Finalize может высвободить блоки памяти, которые выделены отдельно от самого объекта пула.
28 Автор пользовательских процедур выделения и освобождения памяти и пользователи аллокаторов для связанного типа доступа несут ответственность за взаимодействие с задачами. В частности:
Если аллокаторы используются в разных задачах, им требуется взаимное исключение.
Если они используются внутри защищенных объектов, они не могут блокироваться.
Если они используются обработчиками прерываний (см. C.3, «Поддержка прерываний»), механизм взаимного исключения должен работать правильно в этом контексте.
29 Примитивы 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 : MR_Pool.Subpool_Handle; -- См. 13.11.6
My_Mark : MR_Pool.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); --Завершить работу с объектами и высвободить память.