Как работает delete c
Перейти к содержимому

Как работает delete c

[C++] Всё ли мы знаем об операторах new и delete?

Привет! Ниже речь пойдет об известных всем операторах new и delete, точнее о том, о чем не пишут в книгах (по крайней мере в книгах для начинающих).
На написание данной статьи меня побудило часто встречаемое заблуждение по поводу new и delete, которое я постоянно вижу на форумах и даже(. ) в некоторых книгах.
Все ли мы знаем, что такое на самом деле new и delete? Или только думаем, что знаем?
Эта статья поможет вам разобраться с этим (ну, а те, кто знают, могут покритиковать:))

Note: ниже пойдет речь исключительно об операторе new, для других форм оператора new и для всех форм оператора delete все ниженаписанное также является правдой и применимо по аналогии.

Итак, начнем с того, что обычно пишут в книгах для начинающих, когда описывают new (текст взят «с потолка», но вцелом соответствует правде):

И для примера показывают примитивную перегрузку (реализацию) оператора new, прототип которого выглядит так
void* operator new (std::size_t size) throw (std::bad_alloc);

На что хочется обратить внимание:
1. Нигде не разделяют new key-word языка С++ и оператор new, везде о них говорят как об одной сущности.
2. Везде пишут, что new вызывает конструктор(ы) для объекта(ов).
И первое и второе является распространенным заблуждением.

Но не будем надеяться на книги для начинающих, обратимся к Стандарту, а именно к разделу 5.3.4 и к 18.6.1, в которых собственно и раскрывается (точнее приоткрывается) тема данной статьи.

Тут мы уже видим, что в первом случае new именуется как expression, а во втором он объявлен как operator. И это действительно 2 разные сущности!
Попробуем разобраться почему так, для этого нам понадобятся ассемблерные листинги, полученные после компиляции кода, использующего new. Ну, а теперь обо все по порядку.

new-expression — это оператор языка, такой же как if, while и т.д. (хотя if, while и т.д. все же именуются как statement, но отбросим лирику) Т.е. встречая его в листинге компилятор генерирует определенный код, соответствующий этому оператору. Так же new — это одно из key-words языка С++, что еще раз подтверждает его общность с if‘ами, for’ами и т.п. А operator new() в свою очередь — это просто одноименная функция языка С++, поведение которой можно переопределить. ВАЖНОoperator new() НЕ вызывает конструктор(ы) для объекта(ов), под который(ые) выделяется память. Он просто выделяет память нужного размера и все. Его отличие от сишных функций в том, что он может бросить исключение и его можно переопределить, а так же сделать оператором для отдельно взятого класса, тем самым переопределить его только для этого класса (остальное вспомните сами:)).
А вот new-expression как раз и вызывает конструктор(ы) объекта(ов). Хотя правильней сказать, что он тоже ничего не вызывает, просто, встречая его, компилятор генерирует код вызова конструктора(ов).

Для полноты картины рассмотрим следующий пример:

после исполнения данного кода, как и ожидалось, будет напечатано «Foo()». Разберемся почему, для этого понадобится заглянуть в ассемблер, который я немного прокомментировал для удобства.
(код получен компилятором cl, используемым в MSVS 2012, хотя в основном я использую gcc, но это к делу не относится)

Для тех, кто ничего не понял, вот (почти) аналог того, что получилось на сиподобном псевдокоде (т.е. не надо пробовать это компилировать :))

Приведенный код подтверждает все, написанное выше, а именно:
1. оператор (языка) new и operator new() — это НЕ одно и тоже.
2. operator new() НЕ вызывает конструктор(ы)
3. вызов конструктора(ов) генерирует компилятор, встречая в коде key-word «new»

Итог: надеюсь, эта статья помогла вам понять разницу между new-expressionи operator new() или даже узнать, что она (эта разница) вообще существует, если кто-то не знал.

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

Update:
Хабражитель с ником khim в личной переписке предложил следующий код, который хорошо демонстрирует суть написанного выше.

Оператор delete (C++)

Аргумент приведения выражения должен быть указателем на блок памяти, ранее выделенный для объекта, созданного с помощью нового оператора. Оператор delete имеет результат типа void и, следовательно, не возвращает значение. Пример:

Использование delete указателя на объект, не выделенный с непредсказуемыми new результатами. Однако можно использовать delete указатель со значением 0. Эта подготовка означает, что при new возвращении 0 при сбое удаление результата неудачной new операции безвредно. Дополнительные сведения см . в разделе «Новые и удаленные операторы».

Операторы new и delete операторы также можно использовать для встроенных типов, включая массивы. Если pointer ссылается на массив, поместите пустые скобки ( [] ) перед pointer :

delete Использование оператора для объекта освобождает память. Программа, которая разыменовывает указатель после удаления объекта, может создать непрогнозируемый результат или вызвать сбой.

Когда delete используется для освобождения памяти для объекта класса C++, деструктор объекта вызывается до освобождения памяти объекта (если объект имеет деструктор).

Если операнд delete оператору является изменяемым l-значением, его значение не определено после удаления объекта.

Если указан параметр компилятора /sdl (включение дополнительных проверок безопасности), операнд delete оператору присваивается недопустимое значение после удаления объекта.

Использование оператора delete

Существует два синтаксических варианта для оператора delete: один для отдельных объектов, а другой для массивов объектов. В следующем фрагменте кода показано, как они отличаются:

В следующих двух случаях возникают неопределенные результаты: использование формы массива удаления ( delete [] ) для объекта и использование неаррейской формы удаления в массиве.

Пример

Примеры использования delete см. в разделе «Новый оператор».

Принцип работы delete

Оператор delete вызывает удаление оператора функции.

Для объектов, не относится к типу класса (классу, структуре или объединению), вызывается глобальный оператор удаления. Для объектов типа класса имя функции освобождения разрешается в глобальной области, если выражение delete начинается с оператора разрешения унарной области ( :: ). В противном случае перед освобождением памяти оператор удаления вызывает деструктор объекта (если указатель не имеет значения null). Оператор удаления можно определять отдельно для каждого класса; если для некоторого класса такое определение отсутствует, вызывается глобальный оператор удаления. Если выражение удаления используется для освобождения объекта класса, статический тип которого имеет виртуальный деструктор, функция освобождение разрешается через виртуальный деструктор динамического типа объекта.

Как устроены операторы delete и delete[]?

Скажите, пожалуйста, как именно работают операторы delete и delete[] ?

Например, если я сделаю так:

Сможет ли оператор delete корректно удалить объект через бестиповый указатель? То есть, использует ли оператор delete информацию, доступную во время компиляции?

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

Если в delete приходит указатель «неверного типа», то будет вызван и «неверный деструктор». В некоторых случаях это может закончится плачевно. У void деструктор фактически отсутствует, значит вызывать нечего и соответственно ничего не вызовется. Если деструктор выполняет нетривиальную работу — будет не очень хорошо.

delete[] получив указатель, «магическим способом» получает размер массива (обычно, он хранится рядом с указателем, но это личное дело компилятора, поэтому я и написал «магическим способом»). А дальше просто в цикле вызываем delete . Ничего необычного. Поэтому, если попутать delete и delete[] могут быть разные непонятные баги ( delete[] будет искать размер массива там, где его нет, а потом пойдет в разнос).

Некоторые компиляторы могут добавлять немножко больше кода для проверки себя и удалять корректно. К примеру, так может сделать visual studio в дебажном режиме. Но я бы на это не закладывался.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *