Почему std:: allocator:: deallocate требует размера?
std::allocator
представляет собой абстракцию над базовой моделью памяти, которая обертывает функциональные возможности вызова new
и delete
. delete
не нужен размер, но deallocate() требует его.
void deallocate (T * p, std:: size_t n);
"Аргумент n должен быть равен первому аргументу вызова allocate(), изначально созданный p; в противном случае поведение undefined."
Почему?
Теперь мне нужно либо выполнить дополнительные вычисления перед освобождением, либо начать хранить размеры, которые я передал для выделения. Если бы я не использовал распределитель, мне бы не пришлось это делать.
Ответы
Ответ 1
Дизайн API std::allocator
- Allocator
- должен облегчить работу потенциальных замен.
std::allocator
представляет собой абстракцию над базовой моделью памяти
Это не обязательно! В общем случае распределителю не нужно использовать C malloc
и free
, а не delete
или не-на месте new
. Да, обычно используется по умолчанию, но механизм распределителя - это не просто абстракция над моделью памяти C. Быть разными - это целая цель пользовательского распределителя. Помните, что распределители сменны: конкретному std::allocator
может не понадобиться размер для освобождения, но любые замены, вероятно, будут.
Соответствующая реализация std::allocator
позволяет утверждать, что вы действительно передаете правильный n
в deallocate
и в противном случае зависит от правильного размера.
Бывает, что malloc
и free
сохраняют размер блока в своих структурах данных. Но в целом распределитель может этого не делать, и для этого требуется преждевременная пессимизация. Предположим, что у вас есть собственный распределитель пулов и выделяют куски int
s. В типичной 64-битной системе было бы 200% накладных расходов на хранение 64-разрядного size_t
вместе с 32-разрядным int
. Пользователь распределителя гораздо лучше позиционируется как для хранения размера в распределении, так и для определения размера более дешевым способом.
Хорошие реализации malloc не сохраняют размер распределения для каждого небольшого распределения; они и могут вывести размер куска из самого указателя, например. путем получения указателя блока из указателя куска, а затем проверки заголовка блока для размера блока. Это, но, конечно, деталь. Вы можете получить нижнюю границу размера, используя API-интерфейсы платформы, такие как malloc_size
на OS X, _msize
в Windows, malloc_usable_size
в Linux.
Ответ 2
Для алгоритмов распределения памяти часто полезно минимизировать количество требуемых служебных данных. Некоторые алгоритмы, которые отслеживают свободные области, а не выделенные области, могут уменьшить общий объем накладных расходов до низкого значения с нулевыми накладными расходами (данные о хранении хранятся полностью в свободных зонах). В системах, использующих такие алгоритмы, запрос на распределение удаляет хранилище из свободного пула, а запрос на выделение выделяет хранилище в свободный пул.
Если запросы распределения для 256 и 768 байтов удовлетворяются с использованием смежной области пула, состояние менеджера памяти будет идентично тому, что было бы, если бы два запроса на 512 байтов были удовлетворены с использованием той же самой области. Если диспетчер памяти был передан указатель на первый блок и попросил его освободить, у него не было бы возможности узнать, был ли первый запрос для 256 байтов, или 512 байтов или любого другого номера, и, следовательно, нет способа узнать сколько памяти нужно добавить обратно в пул.
Реализация "malloc" и "free" в такой системе потребует, чтобы она хранила длину каждого блока в начале области хранения и возвращала указатель на следующий подходящий адрес, который будет доступен после этого длина. Конечно, это возможно для реализации, но это добавит 4-8 байтов накладных расходов для каждого распределения. Если вызывающий абонент может указать процедуру деаллоляции, сколько хранилищ будет добавлено обратно в пул памяти, такие издержки могут быть устранены.
Ответ 3
Кроме того, довольно легко разместить эту точку проектирования: просто выделите вещи как struct
и сохраните размер как элемент внутри struct
. Ваш код, вызывающий дезактиватор, теперь знает, какое значение предоставить, поскольку сама структура содержит его.
Выполняя это, вы, в основном, делаете то, что может сделать для вас любая другая реализация языка. Вы просто делаете то же самое явно.
Теперь, учитывая, что мы говорим о С++, в котором есть куча больших классов контейнеров, которые уже были встроены, я бы откровенно рекомендовал вам избегать "катиться самостоятельно", если вы можете избежать этого. Просто найдите способ использования одного из классных классов-контейнеров, которые уже предоставляют язык и стандартная библиотека.
В противном случае обязательно упакуйте то, что вы строите здесь, как домашний класс контейнеров. Убедитесь, что логика, которая имеет дело с распределителем и деллалокатором, встречается только один раз в вашей программе. (А именно, внутри этого класса.) Посыпайте его щедро логикой, которая специально предназначена для обнаружения ошибок. (Например, значение часового, которое вставляется в объект при его назначении, и которое должно быть найдено, когда объект освобождается, и который уничтожается непосредственно перед его выполнением. Явные проверки сохраненного значения размера для создания что это имеет смысл. И так далее.)
Ответ 4
Вам не требуется , чтобы отслеживать размер. Стандартный распределитель не отслеживает размер, потому что он принимает размер для всех распределений. Конечно, существуют разные типы распределителей для разных целей. Блок распределения размера блока, как вы могли догадаться, имеет фиксированный размер. Некоторые приложения, такие как видеоигры, заранее распределяют память и устраняют накладные расходы, требуя отслеживания размера для каждого распределения.
Стандартная библиотека пытается как можно более сильная . Некоторым распределителям необходимо отслеживать размер, другие - нет, но все распределители должны соответствовать интерфейсу.
Ответ 5
У меня нет убедительного доказательства, но мое чувство кишки заключается в том, что распределителю не требуется использовать оператор С++ new/delete и может также использовать процедуры управления памятью, которые не имеют возможности выделять массивы и знать их размеры - например, malloc.