Foo* set = new Foo[100];
// ...
delete [] set;
Вы не передаете границы массива delete[]. Но где хранится эта информация? Это стандартизировано?
Обратите внимание, что FastMM относится только к компиляторам Delphi / C++ Builder, это не универсальный диспетчер памяти для C++. Он даже не написан на C++.





Это не то, что указано в спецификации - это зависит от реализации.
Потому что массив, который нужно «удалить», должен был быть создан с помощью однократного использования оператора «новый». «Новая» операция должна была поместить эту информацию в кучу. В противном случае, как дополнительные пользователи new узнают, где заканчивается куча?
Когда вы выделяете память в куче, ваш распределитель будет отслеживать, сколько памяти вы выделили. Обычно это сохраняется в «головном» сегменте непосредственно перед выделенной вам памятью. Таким образом, когда пришло время освободить память, де-распределитель точно знает, сколько памяти нужно освободить.
Обратите внимание, что это применимо только к распределению массивов в C++. Все остальные распределения зависят от размера типа. Некоторые библиотеки хранят все размеры выделения, обычно только в режиме отладки.
Нет абсолютно никаких причин, по которым эта информация не должна быть доступна программисту. Я могу передать указатель на функцию и освободить его, но чтобы самому получить размер в той же функции, мне нужно передать дополнительный параметр. Имеет ли это хоть какой-то смысл?
@Mark, это имеет смысл маленький, потому что теоретически он освобождает распределитель, чтобы всегда хранить размер блока выделенный (который может отличаться от размера блока просил). Некоторым проектам распределителя эта информация может потребоваться для своих собственных целей, или они могут быть недостаточно сложными, чтобы использовать информацию о типе для отслеживания размера распределения кучи, не являющегося массивом, и т. д. Принуждение распределителя хранить запрошенный размер (чтобы вы не необходимо передать размер массива самостоятельно) может быть небольшим препятствием, но это может повлиять на производительность в возможных конструкциях распределителя.
Извините, но этот ответ упускает суть. По сути, QuantumPete описал «Как free знает, сколько памяти нужно освободить». Да, размер блока памяти хранится «где-то» malloc (обычно в самом блоке), так что free знает. Однако с new[] / delete[] совсем другое дело. Последние в основном работают поверх malloc / free. new[] также сохраняет количество созданных элементов в блоке памяти (независимо от malloc), так что позже delete[] может получить и использовать этот номер для вызова нужного количества деструкторов.
Т.е. физически в блоке хранятся два счетчика: размер блока (по malloc) и количество элементов (по new[]). Обратите внимание, что первое не может использоваться для вычисления второго, поскольку в общем случае размер блока памяти может быть больше, чем действительно необходимо для массива запрошенного размера. Также обратите внимание, что счетчик элементов массива нужен только для типов с нетривиальным деструктором. Для типов с тривиальным деструктором счетчик не сохраняется new[] и, конечно же, не извлекается delete[].
Однако точка зрения Марка остается неизменной. Поскольку эта информация должна быть сохранена, нет причин, по которым мы также не можем получить к ней доступ. Если он превысит доступность, vector мог бы использовать это в своих интересах, но в настоящее время vector должен выполнять бессмысленные дополнительные выделения и копии. Если он не превышает доступный объем, то мы можем получить размер массива динамических массивов. Любой из них или оба были бы полезной информацией.
@Mark, если вы используете A* = new B(); delete A;, как компилятор узнает, какой объем памяти нужно освободить?
@ sasha.sochka: компилятор не знает. delete A просто вызывает деструктор A (если он виртуальный, полиморфизм вызывает деструктор B), а затем передает блок памяти, на который указывает A, в менеджер памяти для освобождения. менеджер памяти знает, сколько памяти он выделил для удовлетворения вызова new B().
@MooingDuck Это неправда, что его «надо» хранить. Есть распределители, которые не могут всегда определять размер каждого блока. Например, партнерские распределители могут быть спроектированы так, что они не могут определять размер «нечетного» блока, если соответствующий ему «четный» блок не свободен.
@DavidSchwartz достаточно хорош для вещей с тривиальными деструкторами. Для нетривиальных деструкторов распределитель должен хранить количество элементов.
@MooingDuck Только для распределителя массива, в противном случае количество элементов всегда равно единице.
Проголосовали против, потому что, как заметил AnT, он полностью упускает суть и не отвечает на вопрос. Это действительно должно быть удалено (хех) или исправлено, чтобы дать правильный ответ (как указано во втором комментарии AnT).
Где «головной» сегмент?
@AnT Может ли реализация использовать распределитель, который всегда распределяет блоки таким образом, чтобы размер массива можно было вычислить по размеру блока? Т.е. для T[] количество «лишнего» пространства в блоке всегда будет меньше sizeof(T), что позволит среде выполнения вычислить размер массива с использованием усеченного деления. Или реализация не может требовать такого свойства распределителя?
@ Кайл Стрэнд: Вероятно, это вполне возможно. Скажем, реализация выравнивает размер каждого блока по границе W байтов. Затем для массива T[N] (S = sizeof(T[N])) он может выделить правильно выровненное количество байтов B = (S + W - 1) / W * W, но сохранить в заголовке блока фактически запрошенное количество байтов S. Тогда размер массива будет правильно вывести из сохраненного значения S. Фактический размер выровненного блока B также легко вывести из S.
@AnT На днях я разговаривал с разработчиком LLVM, который подтвердил, что именно так new[] и delete[] реализуются по умолчанию.
Это не стандартизировано. В среде выполнения Microsoft оператор new использует malloc (), а оператор удаления - free (). Итак, в этой настройке ваш вопрос эквивалентен следующему: как free () узнает размер блока?
За кулисами ведется некоторая бухгалтерия, то есть во время выполнения C.
Не правда. Перед вызовом free, delete [] должен сначала вызвать деструкторы. Для этого недостаточно знать общий размер распределений. На самом деле new [] и delete [] работают по-разному для простых и разрушенных типов в VC++.
Информация не стандартизирована. Однако на платформах, над которыми я работал, эта информация хранится в памяти непосредственно перед первым элементом. Поэтому теоретически вы можете получить к нему доступ и проверить, но это того не стоит.
Также вот почему вы должны использовать delete [], когда вы выделяете память с помощью new [], так как версия массива delete знает, что (и где) ей нужно искать, чтобы освободить правильный объем памяти - и вызвать соответствующее количество деструкторов для объектов.
Он определен в стандарте C++ как специфичный для компилятора. Что означает волшебство компилятора. Он может выйти из строя из-за нетривиальных ограничений выравнивания по крайней мере на одной основной платформе.
Вы можете подумать о возможных реализациях, осознав, что delete[] определен только для указателей, возвращаемых new[], которые могут отличаться от указателей, возвращаемых operator new[]. Одна из реализаций в дикой природе - сохранить счетчик массива в первом int, возвращаемом operator new[], и заставить new[] вернуть смещение указателя после этого. (Вот почему нетривиальные выравнивания могут нарушить new[].)
Имейте в виду, что operator new[]/operator delete[]! = new[]/delete[].
Кроме того, это ортогонально тому, как C знает размер памяти, выделенной malloc.
В основном это расположено в памяти как:
[информация] [запомните ...]
Где информация - это структура, используемая вашим компилятором для хранения объема выделенной памяти, а что нет.
Однако это зависит от реализации.
ОДИН ИЗ подходов для компиляторов - выделить немного больше памяти и сохранить количество элементов в элементе заголовка.
Пример того, как это можно сделать:
Здесь
int* i = new int[4];
компилятор выделит sizeof(int)*5 байт.
int *temp = malloc(sizeof(int)*5)
Сохранит "4" в первых байтах sizeof(int).
*temp = 4;
и установите i
i = temp + 1;
Таким образом, i будет указывать на массив из 4 элементов, а не 5.
И удаление
delete[] i;
будет обрабатываться следующим образом:
int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements
... that are stored in temp + 1, temp + 2, ... temp + 4 if needed
free (temp)
Можно ли изменить это, чтобы получить длину массива во время выполнения?
Это более интересная проблема, чем вы могли подумать сначала. Этот ответ касается одной из возможных реализаций.
Во-первых, хотя на каком-то уровне ваша система должна знать, как «освободить» блок памяти, базовый malloc / free (который обычно вызывает new / delete / new [] / delete []) не всегда точно помнит, сколько памяти вы просите, его можно округлить в большую сторону (например, когда вы превысите 4K, оно часто округляется до следующего блока размером 4K).
Следовательно, даже если бы можно было получить размер блока памяти, это не говорит нам, сколько значений находится в новой [] ed памяти, поскольку оно может быть меньше. Следовательно, нам нужно сохранить дополнительное целое число, сообщающее нам, сколько значений существует.
ИСКЛЮЧЕНИЕ, если конструируемый тип не имеет деструктора, то delete [] не должен делать ничего, кроме освобождения блока памяти, и, следовательно, не должен ничего хранить!