Это вопрос, который не дает мне покоя в течение некоторого времени. Я всегда считал, что C++ должен быть спроектирован так, чтобы оператор delete (без скобок) работал даже с оператором new[].
На мой взгляд, пишу это:
int* p = new int;
должен быть эквивалентен размещению массива из 1 элемента:
int* p = new int[1];
Если бы это было так, оператор delete всегда мог бы удалять массивы, и нам бы не понадобился оператор delete[].
Есть ли причина, по которой оператор delete[] был введен в C++? Единственная причина, по которой я могу думать, заключается в том, что выделение массивов требует небольшого объема памяти (вы должны где-то хранить размер массива), поэтому различие delete и delete[] было небольшой оптимизацией памяти.





Это так, что будут вызываться деструкторы отдельных элементов. Да, для массивов POD нет большой разницы, но в C++ вы можете иметь массивы объектов с нетривиальными деструкторами.
Теперь ваш вопрос: почему бы не заставить new и delete вести себя как new[] и delete[] и не избавиться от new[] и delete[]? Я бы вернулся к книге Страуструпа «Дизайн и эволюция», где он сказал, что если вы не используете функции C++, вам не придется за них платить (по крайней мере, во время выполнения). В нынешнем виде new или delete будут вести себя так же эффективно, как malloc и free. Если бы delete имел значение delete[], были бы некоторые дополнительные накладные расходы во время выполнения (как указал Джеймс Карран).
Существует ли на самом деле современная реализация, которая не может определить (если стандарт позволяет) правильную вещь во время выполнения, независимо от того, используется ли delete или delete []?
delete [] гарантирует, что деструктор каждого члена вызывается (если это применимо к типу), в то время как delete просто удаляет память, выделенную для массива.
Вот хорошее чтение: http://www.informit.com/guides/content.aspx?g=cplusplus&seqNum=287
И нет, размеры массивов нигде в C++ не хранятся. (Спасибо всем за указание на неточность этого утверждения.)
Не согласен с вашим последним утверждением. Компилятор должен знать размер массива, чтобы вызывать деструктор для каждого объекта в массиве. Я думаю, вы путаете это с тем фактом, что C++ не выполняет проверку границ массивов.
О, правда. Я думал, вы предлагали сохранить размер как часть структуры данных массива (буфера). Да, компилятору, вероятно, придется где-то хранить информацию о размере ...
Один из подходов - сохранить размер и количество элементов в слове перед началом массива. Это называется файлом cookie.
Кроме того, delete вызывает деструктор - для одного элемента.
У Маршалла Клайна есть информация по этой теме.
Эта ссылка не объясняет, почему для языка были разработаны отдельные операторы delete и delete[].
Поскольку все остальные, кажется, упустили суть вашего вопроса, я просто добавлю, что у меня была такая же мысль несколько лет назад, и я так и не смог получить ответа.
Единственное, о чем я могу думать, это то, что есть очень крошечные дополнительные накладные расходы на обработку одного объекта как массива (ненужный "for(int i=0; i<1; ++i)").
Плюс крошечный бит памяти для хранения размера.
Да, я готов поспорить, что накладные расходы на память считались недопустимыми. Возможно петля тоже была.
Черт, я пропустил всю суть вопроса, но оставлю свой исходный ответ в качестве примечания. У нас есть delete[], потому что давным-давно у нас был delete[cnt], даже сегодня, если вы пишете delete[9] или delete[cnt], компилятор просто игнорирует вещи между [], но компилирует нормально. В то время C++ сначала обрабатывался интерфейсом, а затем передавался в обычный компилятор C. У них не было возможности хранить счет где-то под занавеской, возможно, они даже не могли подумать об этом в то время. И для обратной совместимости компиляторы, скорее всего, использовали значение, указанное между [], как счетчик массива, если такого значения нет, они получили счетчик из префикса, поэтому он работал в обоих направлениях. Потом между [] ничего не набирали, все заработало. Сегодня я не думаю, что delete[] необходим, но реализации этого требуют.
Мой первоначальный ответ (который упускает из виду):
delete удаляет один объект. delete[] удаляет массив объектов. Для работы delete[] реализация сохраняет количество элементов в массиве. Я просто дважды проверил это, отладив код ASM. В тестируемой мной реализации (VS2005) счетчик хранился как префикс к массиву объектов.
Если вы используете delete[] для одного объекта, переменная count будет мусором, поэтому код выйдет из строя. Если вы используете delete для массива объектов, из-за некоторой несогласованности происходит сбой кода. Я тестировал эти кейсы только сейчас!
«delete просто удаляет память, выделенную для массива». утверждение в другом ответе неверно. Если объект является классом, delete вызовет DTOR. Просто поместите точку останова в код DTOR и delete в объект, точка останова сработает.
Мне пришло в голову, что если компилятор и библиотеки предполагают, что все объекты, выделенные new, являются массивами объектов, было бы нормально вызывать delete для отдельных объектов или массивов объектов. Одиночные объекты были бы особым случаем массива объектов, имеющего счетчик 1. Может быть, я все равно чего-то упускаю.
Добавляя это, поскольку на данный момент нет другого ответа:
Массив delete[] нельзя использовать в классе указателя на базовый класс - хотя компилятор сохраняет количество объектов, когда вы вызываете new[], он не сохраняет типы или размеры объектов (как указал Дэвид, в C++ вы редко платите за функцию, которую вы не используете). Однако скалярный delete можно безопасно удалить через базовый класс, поэтому он используется как для обычной очистки объекта, так и для полиморфной очистки:
struct Base { virtual ~Base(); };
struct Derived : Base { };
int main(){
Base* b = new Derived;
delete b; // this is good
Base* b = new Derived[2];
delete[] b; // bad! undefined behavior
}
Однако в противоположном случае - не виртуальный деструктор - скалярный delete должен быть как можно более дешевым - он не должен проверять количество объектов или тип удаляемого объекта. Это делает удаление встроенного типа или простого старого типа данных очень дешевым, поскольку компилятору нужно только вызывать ::operator delete и ничего больше:
int main(){
int * p = new int;
delete p; // cheap operation, no dynamic dispatch, no conditional branching
}
Хотя это не исчерпывающее описание распределения памяти, я надеюсь, что это поможет прояснить широту возможностей управления памятью, доступных в C++.
Меня немного смутил ответ Аарона, и, честно говоря, я не совсем понимаю, зачем и где нужен delete[].
Я провел несколько экспериментов с его образцом кода (после исправления нескольких опечаток). Вот мои результаты.
Опечатки: ~Base требовалось тело функции
Base *b декларировался дважды
struct Base { virtual ~Base(){ }>; };
struct Derived : Base { };
int main(){
Base* b = new Derived;
delete b; // this is good
<strike>Base</strike> b = new Derived[2];
delete[] b; // bad! undefined behavior
}
Составление и исполнение
david@Godel:g++ -o atest atest.cpp
david@Godel: ./atest
david@Godel: # No error message
Модифицированная программа с удаленным delete[]
struct Base { virtual ~Base(){}; };
struct Derived : Base { };
int main(){
Base* b = new Derived;
delete b; // this is good
b = new Derived[2];
delete b; // bad! undefined behavior
}
Составление и исполнение
david@Godel:g++ -o atest atest.cpp
david@Godel: ./atest
atest(30746) malloc: *** error for object 0x1099008c8: pointer being freed was n
ot allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap: 6
Конечно, я не знаю, действительно ли delete[] b работает в первом примере; Я знаю только, что он не выдает сообщения об ошибке компилятора.
На самом деле, когда вы используете new int [1], он сохраняет в заголовке массива перед его первыми данными размер. Таким образом, использование delete вместо delete [] не освободит эту часть памяти.