Как delete [] "знает" размер массива операндов?

Foo* set = new Foo[100];
// ...
delete [] set;

Вы не передаете границы массива delete[]. Но где хранится эта информация? Это стандартизировано?

sourceforge.net/projects/fastmm is open source and replaces the memory-manager. Here you can find out how memory management works and where the informations are come from for allocate and delete memories.
user5598651 02.02.2016 18:46

Обратите внимание, что FastMM относится только к компиляторам Delphi / C++ Builder, это не универсальный диспетчер памяти для C++. Он даже не написан на C++.

Remy Lebeau 27.10.2016 02:18
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
265
2
39 885
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Это не то, что указано в спецификации - это зависит от реализации.

Потому что массив, который нужно «удалить», должен был быть создан с помощью однократного использования оператора «новый». «Новая» операция должна была поместить эту информацию в кучу. В противном случае, как дополнительные пользователи new узнают, где заканчивается куча?

Ответ принят как подходящий

Когда вы выделяете память в куче, ваш распределитель будет отслеживать, сколько памяти вы выделили. Обычно это сохраняется в «головном» сегменте непосредственно перед выделенной вам памятью. Таким образом, когда пришло время освободить память, де-распределитель точно знает, сколько памяти нужно освободить.

Обратите внимание, что это применимо только к распределению массивов в C++. Все остальные распределения зависят от размера типа. Некоторые библиотеки хранят все размеры выделения, обычно только в режиме отладки.

Zan Lynx 13.10.2008 18:30

Нет абсолютно никаких причин, по которым эта информация не должна быть доступна программисту. Я могу передать указатель на функцию и освободить его, но чтобы самому получить размер в той же функции, мне нужно передать дополнительный параметр. Имеет ли это хоть какой-то смысл?

Mark Ruzon 11.06.2009 04:16

@Mark, это имеет смысл маленький, потому что теоретически он освобождает распределитель, чтобы всегда хранить размер блока выделенный (который может отличаться от размера блока просил). Некоторым проектам распределителя эта информация может потребоваться для своих собственных целей, или они могут быть недостаточно сложными, чтобы использовать информацию о типе для отслеживания размера распределения кучи, не являющегося массивом, и т. д. Принуждение распределителя хранить запрошенный размер (чтобы вы не необходимо передать размер массива самостоятельно) может быть небольшим препятствием, но это может повлиять на производительность в возможных конструкциях распределителя.

Doug McClean 24.10.2009 08:27

Извините, но этот ответ упускает суть. По сути, QuantumPete описал «Как free знает, сколько памяти нужно освободить». Да, размер блока памяти хранится «где-то» malloc (обычно в самом блоке), так что free знает. Однако с new[] / delete[] совсем другое дело. Последние в основном работают поверх malloc / free. new[] также сохраняет количество созданных элементов в блоке памяти (независимо от malloc), так что позже delete[] может получить и использовать этот номер для вызова нужного количества деструкторов.

AnT 24.10.2009 08:35

Т.е. физически в блоке хранятся два счетчика: размер блока (по malloc) и количество элементов (по new[]). Обратите внимание, что первое не может использоваться для вычисления второго, поскольку в общем случае размер блока памяти может быть больше, чем действительно необходимо для массива запрошенного размера. Также обратите внимание, что счетчик элементов массива нужен только для типов с нетривиальным деструктором. Для типов с тривиальным деструктором счетчик не сохраняется new[] и, конечно же, не извлекается delete[].

AnT 24.10.2009 08:38

Однако точка зрения Марка остается неизменной. Поскольку эта информация должна быть сохранена, нет причин, по которым мы также не можем получить к ней доступ. Если он превысит доступность, vector мог бы использовать это в своих интересах, но в настоящее время vector должен выполнять бессмысленные дополнительные выделения и копии. Если он не превышает доступный объем, то мы можем получить размер массива динамических массивов. Любой из них или оба были бы полезной информацией.

Mooing Duck 30.04.2012 09:52

@Mark, если вы используете A* = new B(); delete A;, как компилятор узнает, какой объем памяти нужно освободить?

sasha.sochka 14.08.2013 19:09

@ sasha.sochka: компилятор не знает. delete A просто вызывает деструктор A (если он виртуальный, полиморфизм вызывает деструктор B), а затем передает блок памяти, на который указывает A, в менеджер памяти для освобождения. менеджер памяти знает, сколько памяти он выделил для удовлетворения вызова new B().

Remy Lebeau 27.10.2016 02:22

@MooingDuck Это неправда, что его «надо» хранить. Есть распределители, которые не могут всегда определять размер каждого блока. Например, партнерские распределители могут быть спроектированы так, что они не могут определять размер «нечетного» блока, если соответствующий ему «четный» блок не свободен.

David Schwartz 09.12.2016 10:26

@DavidSchwartz достаточно хорош для вещей с тривиальными деструкторами. Для нетривиальных деструкторов распределитель должен хранить количество элементов.

Mooing Duck 10.12.2016 05:26

@MooingDuck Только для распределителя массива, в противном случае количество элементов всегда равно единице.

David Schwartz 10.12.2016 07:26

Проголосовали против, потому что, как заметил AnT, он полностью упускает суть и не отвечает на вопрос. Это действительно должно быть удалено (хех) или исправлено, чтобы дать правильный ответ (как указано во втором комментарии AnT).

Jim Balter 16.10.2017 09:51

Где «головной» сегмент?

scottxiao 30.04.2018 05:37

@AnT Может ли реализация использовать распределитель, который всегда распределяет блоки таким образом, чтобы размер массива можно было вычислить по размеру блока? Т.е. для T[] количество «лишнего» пространства в блоке всегда будет меньше sizeof(T), что позволит среде выполнения вычислить размер массива с использованием усеченного деления. Или реализация не может требовать такого свойства распределителя?

Kyle Strand 16.08.2019 01:26

@ Кайл Стрэнд: Вероятно, это вполне возможно. Скажем, реализация выравнивает размер каждого блока по границе W байтов. Затем для массива T[N] (S = sizeof(T[N])) он может выделить правильно выровненное количество байтов B = (S + W - 1) / W * W, но сохранить в заголовке блока фактически запрошенное количество байтов S. Тогда размер массива будет правильно вывести из сохраненного значения S. Фактический размер выровненного блока B также легко вывести из S.

AnT 16.08.2019 01:47

@AnT На днях я разговаривал с разработчиком LLVM, который подтвердил, что именно так new[] и delete[] реализуются по умолчанию.

Kyle Strand 26.08.2019 21:36

Это не стандартизировано. В среде выполнения Microsoft оператор new использует malloc (), а оператор удаления - free (). Итак, в этой настройке ваш вопрос эквивалентен следующему: как free () узнает размер блока?

За кулисами ведется некоторая бухгалтерия, то есть во время выполнения C.

Не правда. Перед вызовом free, delete [] должен сначала вызвать деструкторы. Для этого недостаточно знать общий размер распределений. На самом деле new [] и delete [] работают по-разному для простых и разрушенных типов в VC++.

Suma 13.10.2008 18:25

Информация не стандартизирована. Однако на платформах, над которыми я работал, эта информация хранится в памяти непосредственно перед первым элементом. Поэтому теоретически вы можете получить к нему доступ и проверить, но это того не стоит.

Также вот почему вы должны использовать 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)

Можно ли изменить это, чтобы получить длину массива во время выполнения?

Jessie Lesbian 22.12.2020 11:46

Это более интересная проблема, чем вы могли подумать сначала. Этот ответ касается одной из возможных реализаций.

Во-первых, хотя на каком-то уровне ваша система должна знать, как «освободить» блок памяти, базовый malloc / free (который обычно вызывает new / delete / new [] / delete []) не всегда точно помнит, сколько памяти вы просите, его можно округлить в большую сторону (например, когда вы превысите 4K, оно часто округляется до следующего блока размером 4K).

Следовательно, даже если бы можно было получить размер блока памяти, это не говорит нам, сколько значений находится в новой [] ed памяти, поскольку оно может быть меньше. Следовательно, нам нужно сохранить дополнительное целое число, сообщающее нам, сколько значений существует.

ИСКЛЮЧЕНИЕ, если конструируемый тип не имеет деструктора, то delete [] не должен делать ничего, кроме освобождения блока памяти, и, следовательно, не должен ничего хранить!

Другие вопросы по теме