Постоянно натыкаюсь на эту непонятку, и, опять же, не понимаю, почему другим это не непонятно
// Иногда приходиться создавать динамические массивы, например:
DWORD* a = new DWORD[20];
// потом производить хитрые операции,
// и, когда этот массив следует удалить,
// я всегда поступаю так:
delete a;
// А, вот, дядя Страустрап и все остальные мегапрограммеры применяют такую хитрую форму:
delete[] a;
// Спрашивается, что нам это даёт и зачем существует в природе?
Здравствуйте, mavius, Вы писали:
M>// Спрашивается, что нам это даёт и зачем существует в природе?
Тут уже обсуждалось не раз, поройся поиском. А если вкратце — то для удаления массивов надо использовать именно delete[]. Во-первых, никто не гарантирует, что в данном конкретном компиляторе delete будет корректно удалять массивы, он же предназначен для удаления единичных объектов (хотя все нормальные компиляторы делают это корректно). А во-вторых (это важно для работы с классами, а не с простыми типами), delete[] вызовет деструкторы для всех элементов массива, а delete — только для одного.
В С++ new и new[] — разные вещи. Тоже относится и к delete/delete []. Поэтому нужно следовать правилу:
выделил new — освободил delete; выделил new [] — освободил delete []. Путаница ведет к неопределенному поведению.
на практике часто бывает, что для встроенных типов связка new[]/delete работает нормально. Но для user-defined типов все далеко не так безоблачно.
Чтобы не быть голословным, вот небольшой примерчик.
#include <iostream>
struct test
{
test() { std::cout << "ctor\n";}
~test() { std::cout << "dtor\n";}
};
int main()
{
test* p = new test[10];
delete p;
return 0;
}
Здравствуйте, mavius, Вы писали:
M>Постоянно натыкаюсь на эту непонятку, и, опять же, не понимаю, почему другим это не непонятно M>// Иногда приходиться создавать динамические массивы, например:
M>
DWORD* a = new DWORD[20];
M>
delete a;
M>// А, вот, дядя Страустрап и все остальные мегапрограммеры применяют такую хитрую форму:
M>
delete[] a;
M>// Спрашивается, что нам это даёт и зачем существует в природе?
При вызове delete вызывается один или несколько деструкторов, а затем посредством функции operator delete освобождается память.
Что случиться если не использовать delete[] для удаления массивов? Неизвестно. Более того, это не определено даже для встроенных типом, подобных int, несмотря на то, что у таких типов нет деструкторов.
Здравствуйте, mavius, Вы писали:
M>Постоянно натыкаюсь на эту непонятку, и, опять же, не понимаю, почему другим это не непонятно
Данетже, delete без [] ты можышь вызывать и для масивов, только деструктор не вызвится,
кажется об этом даже Страуструп говорил, что встроенных типов ты можышь не писать [].
А вот для объектов надо писать, чтобы для всех вызвался деструктор помимо освобождения памяти.
Ну и плюсь читабельность, для нее всё же лучше писать [] если осовобождаешь масив.
Здравствуйте, bayda, Вы писали:
B>Данетже, delete без [] ты можышь вызывать и для масивов, только деструктор не вызвится,
Неверно. Это UB.
B>...кажется об этом даже Страуструп говорил, что встроенных типов ты можышь не писать [].
Ссылку на первоисточник можно привести?
Здравствуйте, Bell, Вы писали:
B>Здравствуйте, bayda, Вы писали:
B>>Данетже, delete без [] ты можышь вызывать и для масивов, только деструктор не вызвится, B>Неверно. Это UB.
что такое UB?
B>>...кажется об этом даже Страуструп говорил, что встроенных типов ты можышь не писать []. B>Ссылку на первоисточник можно привести?
если очень интересно, то я сегодня вечером (после работы) либо завтра сутра
поищу повспоминаю откуда я это взял
B>что такое UB?
Неопределенное поведение (Undefined Behavior).
B>>>...кажется об этом даже Страуструп говорил, что встроенных типов ты можышь не писать []. B>>Ссылку на первоисточник можно привести? B>если очень интересно, то я сегодня вечером (после работы) либо завтра сутра B>поищу повспоминаю откуда я это взял
Было бы любопытно.
Ну а предварительно можно заглянуть в стандарт, пункт 5.3.5/2
Здравствуйте, bayda, Вы писали:
B>Здравствуйте, Bell, Вы писали:
B>>Здравствуйте, bayda, Вы писали:
B>>>Данетже, delete без [] ты можышь вызывать и для масивов, только деструктор не вызвится, B>>Неверно. Это UB. B>что такое UB?
UB — неопределенное поведение — то есть, компилятор вправе сделать все что угодно и ему за это ничего не будет
B>>>...кажется об этом даже Страуструп говорил, что встроенных типов ты можышь не писать []. B>>Ссылку на первоисточник можно привести? B>если очень интересно, то я сегодня вечером (после работы) либо завтра сутра B>поищу повспоминаю откуда я это взял
Кстати говоря, если у класса определены только operator new/operator delete, то new[]/delete[] обратятся не к ним, а к глобальным операторам.
И наоборот, если определены operator new[]/operator delete[], то new/delete одиночного объекта пролетят мимо.
Здравствуйте, mavius, Вы писали:
M>Постоянно натыкаюсь на эту непонятку, и, опять же, не понимаю, почему другим это не непонятно M>// Иногда приходиться создавать динамические массивы, например:
M>
DWORD* a = new DWORD[20];
M>// потом производить хитрые операции, M>// и, когда этот массив следует удалить, M>// я всегда поступаю так:
M>
delete a;
M>// А, вот, дядя Страустрап и все остальные мегапрограммеры применяют такую хитрую форму:
M>
delete[] a;
M>// Спрашивается, что нам это даёт и зачем существует в природе?
Кстати, а не синтаксический ли это оверхед? Компилятор ведь сам может определить что стоит справа указатель на элемент, или указатель на массив.
Здравствуйте, WinterMute, Вы писали:
WM>Кстати, а не синтаксический ли это оверхед? Компилятор ведь сам может определить что стоит справа указатель на элемент, или указатель на массив.
Да ну?
extern void my_free(some_struct* ptr);
int main()
{
my_free(new some_struct());
my_free(new some_struct [10]);
return 0;
}
//там вдали за рекойvoid my_free(some_struct* ptr)
{
//как тут определить, что есть ptr ?
}
Здравствуйте, Bell, Вы писали:
B>Здравствуйте, WinterMute, Вы писали:
WM>>Кстати, а не синтаксический ли это оверхед? Компилятор ведь сам может определить что стоит справа указатель на элемент, или указатель на массив.
B>Да ну?
B>
B>extern void my_free(some_struct* ptr);
B>int main()
B>{
B> my_free(new some_struct());
B> my_free(new some_struct [10]);
B> return 0;
B>}
B>//там вдали за рекой
B>void my_free(some_struct* ptr)
B>{
B> //как тут определить, что есть ptr ?
B>}
B>
А как тут поможет наличие двух операторов delete и delete[]?
Здравствуйте, WinterMute, Вы писали:
WM>А как тут поможет наличие двух операторов delete и delete[]?
Очень просто: для массивов будет вызываться delete[], а для одиночных объектов — delete.
А вот когда что вызывать — вопрос второй. Это может быть к примеру простое соглашение, что мол эта функция используется только для удаления массивов. Можно к примеру передавать дополнительный параметр, но это уже оверхед.
А суть всей проблемы в том, что в С++ типы выражений new int и new int [20] идентичны, и следовательно компилятор тут нам ничем не поможет.
Здравствуйте, WinterMute, Вы писали:
WM>А как тут поможет наличие двух операторов delete и delete[]?
Конечно, поможет.
void destroy( foo* p, // заметим, что в типе нет информации о том, массив это или одиночный объектbool arr
)
{
if(arr)
delete[] p;
else
delete p;
}
int main()
{
destroy( new foo(), false );
destroy( new foo[123], true );
destroy( new foo(), true ); // ах, мы что-то перепутали... и уронили себе на ногу
}
Чтобы такого не случалось, можно запретить явное использование new/delete/new[]/delete[], а написать специальные умные указатели двух различных видов — один для одиночных предметов, другой для массивов. И соответствующие производящие функции.
(Это не обязательно boost::scoped_ptr / scoped_array, потому что политика владения — второй вопрос).
Здравствуйте, Bell, Вы писали:
B>Здравствуйте, WinterMute, Вы писали:
WM>>А как тут поможет наличие двух операторов delete и delete[]? B>Очень просто: для массивов будет вызываться delete[], а для одиночных объектов — delete. B>А вот когда что вызывать — вопрос второй. Это может быть к примеру простое соглашение, что мол эта функция используется только для удаления массивов. Можно к примеру передавать дополнительный параметр, но это уже оверхед. B>А суть всей проблемы в том, что в С++ типы выражений new int и new int [20] идентичны, и следовательно компилятор тут нам ничем не поможет.
Я, конечно, неправильно выразился, не комплятор знает, а менеджер памяти знает. С той оговоркой, что я не очень хорошо представляю как менеджер памяти работает, я всё-таки предположу как оно всё происходит: если мы пишем delete [] arrPtr, то, компилятор встявляет код, который обращается к менеджеру памяти, чтобы узнать, сколько обектов выделено по этому адресу, для всех них вызываются деструкторы, потом менеджер памяти освобождает этот кусок. Если написать delete arrPtr, то, скорее всего вызовется деструктор только первого объекта, память освободится вся. Т.е., если заставить компилятор всегда смотреть, сколько объектов выделено по этому адресу(эта информация всегда доступна), то можно обойтись одним оператором, разумеется, работать это будет чуть медленнее.
Здравствуйте, WinterMute, Вы писали:
WM>Я, конечно, неправильно выразился, не комплятор знает, а менеджер памяти знает. С той оговоркой, что я не очень хорошо представляю как менеджер памяти работает, я всё-таки предположу как оно всё происходит: если мы пишем delete [] arrPtr, то, компилятор встявляет код, который обращается к менеджеру памяти, чтобы узнать, сколько обектов выделено по этому адресу, для всех них вызываются деструкторы, потом менеджер памяти освобождает этот кусок. Если написать delete arrPtr, то, скорее всего вызовется деструктор только первого объекта, память освободится вся. Т.е., если заставить компилятор всегда смотреть, сколько объектов выделено по этому адресу(эта информация всегда доступна), то можно обойтись одним оператором, разумеется, работать это будет чуть медленнее.
WM>Или я жестоко заблужаюсь?
Заблуждаешься.
1) Менеджеры памяти для operator new и operator new[] могут быть вообще разные.
2) Даже если одинаковые, то такой подход сделает невозможным работу placement new — потому что выражение new всегда будет пытаться записать эту информацию в предоставленный буфер.
Здравствуйте, nen777w, Вы писали:
N>для интегральных типов delete[] и delete равноценно.
Ссылочку на стандарт, пожалуйста. Или на документацию к компиляторам, где говорится, что данный случай undefined behavior является implementation-defined.
(Как, например, недавно было указано про void* <-> void(*)()