Безопасно ли перераспределять память, выделенную с помощью нового?
Из того, что написано здесь, new
выделяет в свободном хранилище, а malloc
использует кучу, и два термина часто означают одно и то же.
Из того, что написано здесь, realloc
может переместить блок памяти в новое место. Если свободное хранилище и куча - это два разных пространства памяти, значит ли это любая проблема?
В частности, я хотел бы знать, можно ли использовать
int* data = new int[3];
// ...
int* mydata = (int*)realloc(data,6*sizeof(int));
Если нет, есть ли другой способ realloc
памяти, выделенной с помощью new
безопасно? Я мог бы выделить новую область и memcpy
содержимое, но из того, что я понимаю, realloc
может использовать ту же область, если это возможно.
Ответы
Ответ 1
Вы можете использовать только realloc
, который был выделен с помощью malloc
(или семейства, например calloc
).
Это потому, что базовые структуры данных, которые отслеживают свободные и используемые области памяти, могут быть совершенно разными.
Вероятно, но ни в коем случае нельзя гарантировать, что С++ new
и C malloc
используют один и тот же базовый распределитель, и в этом случае realloc
может работать для обоих. Но формально это в УБ-земле. И на практике это просто бесполезно рискованно.
С++ не предлагает функциональные возможности, соответствующие realloc
.
Ближайшим является автоматическое перераспределение (внутренних буферов) контейнеров, таких как std::vector
.
Контейнеры С++ страдают от разработки таким образом, который исключает использование realloc
.
Вместо представленного кода
int* data = new int[3];
//...
int* mydata = (int*)realloc(data,6*sizeof(int));
& hellip; сделайте следующее:
vector<int> data( 3 );
//...
data.resize( 6 );
Однако, если вам абсолютно нужна общая эффективность realloc
, и если вам нужно принять new
для исходного распределения, то единственным средством для эффективности является использование специальных средств для компилятора, знание которых realloc
безопасен с этим компилятором.
В противном случае, если вам абсолютно необходим общий эффект realloc
, но он не вынужден принимать new
, вы можете использовать malloc
и realloc
. Используя интеллектуальные указатели, вы можете получить большую часть той же безопасности, что и в контейнерах С++.
Ответ 2
Единственное возможное ограничение С++, добавленное к realloc
, заключается в том, что С++ malloc
/calloc
/realloc
не может быть реализовано в терминах ::operator new
, а его free
не может быть реализовано в терминах of ::operator delete
(за С++ 14 [c.malloc] p3-4).
Это означает, что гарантия, которую вы ищете, не существует на С++. Это также означает, что вы можете реализовать ::operator new
в терминах malloc
. И если вы это сделаете, то теоретически результат ::operator new
можно передать в realloc
.
На практике вы должны быть обеспокоены тем, что результат new
не соответствует результату ::operator new
. Компиляторы С++ могут, например, объединить несколько выражений new
для использования одного единственного вызова ::operator new
. Это то, что компиляторы уже делали, когда стандарт не позволял этого, IIRC и стандарт теперь позволяют это (на С++ 14 [expr.new] p10). Это означает, что даже если вы идете по этому маршруту, у вас все еще нет гарантии, что передача указателей new
на realloc
делает что-либо значимое, даже если оно больше не работает undefined.
Ответ 3
В общем, не делайте этого. Если вы используете пользовательские типы с нетривиальной инициализацией, в случае освобождения от копирования-выделений, деструктор ваших объектов не будет называться на realloc
. Копирование конструктора также не будет называться при копировании. Это может привести к поведению undefined из-за неправильного использования времени жизни объекта (см. С++ Standard §3.8. Время жизни объекта, [basic.life]).
1 Время жизни объекта - это свойство времени выполнения объекта. Говорят, что объект имеет нетривиальную инициализацию, если он имеет тип класса или агрегата, и он или один из его членов инициализируется конструктором, отличным от тривиального конструктора по умолчанию. [Примечание: инициализация тривиальным конструктором copy/move является нетривиальной инициализацией. -end note]
Время жизни объекта типа T начинается, когда:
- получено хранилище с надлежащим выравниванием и размером для типа T, а
- если объект имеет нетривиальную инициализацию, его инициализация завершена.
Время жизни объекта типа T заканчивается, когда:
- если T - тип класса с нетривиальным деструктором (12.4), начинается вызов деструктора или
- хранилище, которое занимает объект, повторно используется или освобождается.
И позже (внимание мое):
3 Свойства, приписываемые объектам в рамках этого международного стандарта, применяются для данного объекта только при его жизни.
Итак, вы действительно не хотите использовать объект из своего срока службы.
Ответ 4
Это не безопасно, и это не изящно.
Возможно, можно переопределить новый/удалить, чтобы поддерживать перераспределение, но тогда вы можете также рассмотреть возможность использования контейнеров.
Ответ 5
Да - если new
на самом деле называется malloc
в первую очередь (например, так работает VС++ new
).
Нет. обратите внимание, что как только вы решите перераспределить память (потому что new
называется malloc
), ваш код больше специфичен для компилятора, а не переносится между компиляторами.
(Я знаю, что этот ответ может расстроить многих разработчиков, но я отвечаю, зависит от реальных фактов, а не только от идиоматики).
Ответ 6
Это не безопасно. Во-первых, указатель, который вы передаете на realloc
, должен быть получен из malloc
или realloc
: http://en.cppreference.com/w/cpp/memory/c/realloc.
Во-вторых, результат new int [3]
может не совпадать с результатом функции распределения - дополнительное пространство может быть выделено для хранения количества элементов.
(И для более сложных типов, чем int
, realloc
не будет безопасным, поскольку он не вызывает копирование или перемещение конструкторов.)
Ответ 7
Возможно, вы сможете (не во всех случаях), но не должны. Если вам нужно изменить размер таблицы данных, вы должны использовать std::vector
.
Сведения о том, как его использовать, перечислены в другом SO-вопросе.
Ответ 8
В общем, нет.
Есть множество вещей, которые нужно держать, чтобы сделать их безопасными:
- Побитовое копирование типа и отказ от источника должны быть безопасными.
- Деструктор должен быть тривиальным, или вы должны на месте - уничтожить элементы, которые хотите освободить.
- Любой конструктор тривиален, или вы должны создавать на месте новые элементы.
Тривиальные типы удовлетворяют вышеуказанным требованиям.
Кроме того:
- Функция
new[]
должна передать запрос на malloc
без каких-либо изменений, а также не выполнять бухгалтерский учет сбоку. Вы можете заставить это, заменив глобальные новые [] и delete [], или те, которые находятся в соответствующих классах.
- Компилятор не должен запрашивать больше памяти, чтобы сохранить количество выделенных элементов или что-то еще.
Невозможно заставить это сделать, хотя компилятор не должен сохранять такую информацию, если тип имеет тривиальный деструктор в качестве качества выполнения.
Ответ 9
Эти функции в основном используются в C.
memset устанавливает байты в блоке памяти на определенное значение.
malloc выделяет блок памяти.
calloc, то же, что и malloc. Единственное отличие состоит в том, что он инициализирует байты до нуля.
В С++ предпочтительным методом выделения памяти является использование нового.
C: int intArray = (int *) malloc (10 * sizeof (int));
С++: int intArray = new int [10];
C: int intArray = (int *) calloc (10 * sizeof (int));
С++: int intArray = new int10;