Как delete [] знает, что это массив? - PullRequest
133 голосов
/ 01 апреля 2009

Хорошо, я думаю, что мы все согласны с тем, что то, что происходит со следующим кодом, не определено, в зависимости от того, что передано,

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

Указатель может быть разного рода, поэтому выполнение безусловного delete[] для него не определено. Однако, давайте предположим, что мы действительно передаем указатель массива,

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

Мой вопрос, в этом случае, когда указатель является массивом, кто это знает? Я имею в виду, с точки зрения языка / компилятора, он не имеет представления, является ли arr указателем массива по сравнению с указателем на один тип int. Черт возьми, он даже не знает, был ли arr создан динамически. Тем не менее, если я сделаю следующее вместо этого,

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

Операционная система достаточно умна, чтобы удалять только одно целое число и не использовать какой-либо тип "убийственного веселья", удаляя остальную часть памяти после этой точки (в отличие от strlen и не-1011 * -определенного строка - она ​​будет продолжаться, пока не достигнет 0).

Так чья же работа - помнить эти вещи? Сохраняет ли ОС какой-либо тип записи в фоновом режиме? (Я имею в виду, я понимаю, что начал этот пост, сказав, что то, что происходит, не определено, но на самом деле сценарий «убийства» не происходит, поэтому в практическом мире кто-то помнит .)

Ответы [ 16 ]

102 голосов
/ 01 апреля 2009

Один вопрос, на который пока не даны ответы: если библиотеки времени выполнения (на самом деле не ОС) могут отслеживать количество вещей в массиве, то зачем нам нужен delete[] синтаксис вообще? Почему нельзя использовать одну delete форму для обработки всех удалений?

Ответ на это восходит к корням C ++ как к C-совместимому языку (которым он больше не стремится). Философия Страуструпа заключалась в том, что программисту не нужно платить за любые функции, которые они не используют. Если они не используют массивы, то они не должны нести стоимость массивов объектов для каждого выделенного фрагмента памяти.

То есть, если ваш код просто делает

Foo* foo = new Foo;

тогда пространство памяти, выделенное для foo, не должно включать никаких дополнительных служебных данных, необходимых для поддержки массивов Foo.

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

delete[] bar;

вместо

delete bar;

если bar является указателем на массив.

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

97 голосов
/ 01 апреля 2009

Компилятор не знает, что это массив, он доверяет программисту. Удаление указателя на один int с delete [] приведет к неопределенному поведению. Ваш второй main() пример небезопасен, даже если он не сразу падает.

Компилятор должен отслеживать, сколько объектов нужно каким-либо образом удалить. Это может быть сделано путем перераспределения, достаточного для хранения размера массива. Подробнее см. C ++ Super FAQ .

27 голосов
/ 01 апреля 2009

Да, ОС держит некоторые вещи в «фоновом режиме». Например, если вы запустите

int* num = new int[5];

ОС может выделить 4 дополнительных байта, сохранить размер выделения в первых 4 байтах выделенной памяти и вернуть указатель смещения (то есть, она выделяет области памяти от 1000 до 1024, но возвращенный указатель указывает на 1004 местоположения 1000-1003, хранящие размер выделения). Затем, когда вызывается delete, он может просмотреть 4 байта, прежде чем указатель будет передан ему, чтобы найти размер выделения.

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

13 голосов
/ 01 апреля 2009

Это очень похоже на этот вопрос и содержит много деталей, которые вы ищете.

Но достаточно сказать, что задачей ОС не является отслеживание всего этого. Это на самом деле библиотеки времени выполнения или базовый менеджер памяти, который будет отслеживать размер массива. Обычно это делается путем выделения дополнительной памяти заранее и сохранения размера массива в этом месте (большинство используют головной узел).

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

int* pArray = new int[5];
int size = *(pArray-1);
9 голосов
/ 16 апреля 2009

delete или delete[], вероятно, освободили бы выделенную память (указанная память), но большая разница в том, что delete в массиве не вызовет деструктор каждого элемента массива.

В любом случае, смешивание new/new[] и delete/delete[], вероятно, UB.

6 голосов
/ 01 апреля 2009

Он не знает, что это массив, поэтому вы должны указать delete[] вместо обычного старого delete.

5 голосов
/ 28 января 2010

У меня был похожий вопрос к этому. В C вы выделяете память с помощью malloc () (или другой аналогичной функции) и удаляете ее с помощью free (). Существует только один malloc (), который просто выделяет определенное количество байтов. Существует только одна функция free (), которая просто принимает указатель в качестве параметра.

Так почему же в C вы можете просто передать указатель на free, а в C ++ вы должны указать ему, является ли он массивом или единственной переменной?

Ответ, как я узнал, связан с деструкторами классов.

Если вы выделите экземпляр класса MyClass ...

classes = new MyClass[3];

И удалить его с помощью delete, вы можете получить деструктор только для первого экземпляра MyClass. Если вы используете delete [], вы можете быть уверены, что деструктор будет вызываться для всех экземпляров в массиве.

Это важное отличие. Если вы просто работаете со стандартными типами (например, int), вы действительно не увидите эту проблему. Кроме того, вы должны помнить, что поведение при использовании delete для new [] и delete [] для new не определено - оно может работать не одинаково на всех компиляторах / системах.

3 голосов
/ 24 февраля 2014

Один из подходов для компиляторов - выделить немного больше памяти и сохранить количество элементов в элементе head.

Пример, как это можно сделать: Здесь

int* i = new int[4];

компилятор выделит размер (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 if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)
3 голосов
/ 01 апреля 2009

Это зависит от времени выполнения, которое отвечает за распределение памяти, так же, как вы можете удалить массив, созданный с помощью malloc в стандартном C, используя free. Я думаю, что каждый компилятор реализует это по-своему. Один из распространенных способов - выделить дополнительную ячейку для размера массива.

Однако среда выполнения не достаточно умна, чтобы определить, является ли она массивом или указателем, вы должны сообщить об этом, и если вы ошибаетесь, вы либо не удаляете правильно (например, ptr вместо массива ), или вы в конечном итоге берете несвязанное значение для размера и наносите значительный ущерб.

1 голос
/ 06 мая 2011

Согласитесь, что компилятор не знает, массив это или нет. Это зависит от программиста.

Компилятор иногда отслеживает, сколько объектов необходимо удалить, перераспределяя их достаточно для хранения размера массива, но это не всегда необходимо.

Для полной спецификации, когда выделено дополнительное хранилище, обратитесь к C ++ ABI (как реализованы компиляторы): Itanium C ++ ABI: новые файлы cookie оператора массива

...