воскресенье, января 20, 2008

Зачем нужно выражение delete[]

Смысл существования delete[] мне всегда был не до конца ясен. Нет, понятно, что он нужен для освобождения массивов памяти, выделенных посредством new[], но почему бы все удаление не интегрировать в простой delete?

А так программисту приходится всегда помнить, что именно вызывать для освобождения памяти: delete или delete[]. Зачем - не очень понятно. Информация о том, сколько именно элементов в массиве все равно где-то да хранится. Почему бы компилятору вообще не освободить программиста от каких-либо запоминаний?

Все, что мне удалось раскопать по этому поводу - старое обсуждение в comp.lang.c++
Why does delete[] exist?

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

Но экономия получается уж очень несущественная. Ну разве что для мобильных устройств она может оказаться значимой. И многие с большим удовольствием отказались бы от этой экономии, ради того чтобы не ловить таинственные глюки и утечки памяти долгими часами из-за каких-то пропущенных скобочек. Простого delete'а вполне достаточно. Более того, по словам участников вышеупомянутой дискуссии, именно так работали ранние Борландовские компиляторы.

Ссылки по теме:
Что делает выражение вида delete p

52 коммент.:

Анонимный комментирует...

все гораздо страшнее, когда один программер выделяет память malloc а другой освобождает ее с помощью delete :))

CyberZX комментирует...

я уже давным давно в коде не использую ни delete, ни delete[] :)

black zorro комментирует...

Я начинал где-то во времена borland c++ 3.1. И там точно был delete [].

А можно подробнее про скорость? На что в приниципе влияют эти скобочки (это ведь только этап компиляции)?

Raider комментирует...

Покажите пальцем - кто сейчас пишут руками delete или delete[]? Давно изобретены умные указатели. Соответственно, new/delete или new[]/delete[] встречается только внутри классов типа SomeAllocator размером в несколько [десятков] строк. На таком коротком участке кода забыть что использовать - со скобочками или без - очень сложно.

Алёна комментирует...

2black zorro:
Я начинал где-то во времена borland c++ 3.1. И там точно был delete [].

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

А можно подробнее про скорость?

Если обойтись одним delete'ом, то придется делать проверку что удаляем - массив или не массив, это будет занимать какое-то время.

На что в приниципе влияют эти скобочки (это ведь только этап компиляции)?

Про это я уже как-то писала тут.

Алёна комментирует...

2Raider:
Покажите пальцем - кто сейчас пишут руками delete или delete[]?

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

Вообще в графике, где всегда нужна скорость зачастую не используют умные указатели, потому что считается, что это медленно. Хотя это бывает не всегда оправдано.

На таком коротком участке кода забыть что использовать - со скобочками или без - очень сложно.

Человек очень ненадежен в этом смысле. Один раз не забудет, второй не забудет, на десятый раз забудет.

Prokrust комментирует...

Информация о том, сколько именно элементов в массиве все равно где-то да хранится.
А где? Я бы реализуя оператор new для массивов помещал бы количество элементов перед первым элементом массива. В этом случае реализация delete должна знать был выделен массив или элемент чтобы правильно удалить. И экономия в 4 байта при выделении памяти для одного объекта вполне существенна.
ЗЫ. Вообще то не вижу проблемы, так как в режиме отладки эту ошибку легко отловить. Вывод - пишите тесты!

Евгений Железников комментирует...

Возможно, стоит предложить обдумать тему комиссии разработки нового стандарта С++ 09?

sse комментирует...

Заранее прошу из-за изгороди камнями не кидаццо )

Во-первых, перед с++ было поставлено требование поддерживать совместимость с с (си)(sic!). Вопрос - при чем здесь delete[]? при том, что указатели тоже должны отвечать требованиям совместимости. а это sizeof(ptrdiff_t), и точка. Так что при настоящем положении вещей никаких тегов перед указателем ввести не удастся. Более того, с учетом допустимости конструкции

int m[];
int * p = m; // or int * p = &m[0];

в рантайме невозможно в принципе узнать, какой тег следует приписать указателю - массив или нет. Эта проблема проистекает из того факта, что в с++ массивы не есть сущности "первого рода", этапа выполнения, а скорее этакая вещь в себе, представленная метаинформацией, понятной компилятору (но не разработчику, даже если он понимает, что делает :))

Вот поэтому придется пока мириться. Или пересматривать стандарт, что, к сожалению, без breaking changes не обойдется.

ilya комментирует...

Меня больше волновал другой вопрос. При использовании delete[] очевидно, что компилятор использует некий механизм, который знает сколько элементов нужно удалить, Следовательно так или иначе в рантайме всегда есть информация о том, сколько элементов содержится в массиве по данному указателю. Почему бы не ввести в язык возможность узнавать эту длину? Вместо такого удобства приходится постоянно передавать массивы вместе с указанием кол-ва элементов в них.

Yuriy Volkov комментирует...

delete[] обязательно должен быть, иначе компилятор бы не соответствовал Стандарту. Интересно как там отрабатывал delete на массивах.
delete[] там был, но компилятор не соответствовал стандарту так как в те времена еще стандарта не было (1992 год). namespace, std, string - нету. Там много чего вообще нет. Так что нечему тут удивляться ;-)

sse комментирует...

2ilya:

опять же, из за невозможности это сделать в рантайме.

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

Как узнать, на что указывает - только проверить; проверка же нетривиальна и требует затрат (как и все связанное с управлением памятью). Поэтому все остается на совести программиста. В угоду производительности.

Анонимный комментирует...

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

Алёна комментирует...

2Prokrust:
Информация о том, сколько именно элементов в массиве все равно где-то да хранится.

А где?


В C++ FAQ Lite есть описание двух алгоритмов: over-allocation и associative array

2Евгений Железников:
Возможно, стоит предложить обдумать тему комиссии разработки нового стандарта С++ 09?

Они уже новые предложения не принимают, так что поздно. Вообще надо глянуть, может что-нибудь подобное и обсуждалось, но было отклонено по каким-то причинам. Но я ни о чем таком не слышала.

2sse:
Заранее прошу из-за изгороди камнями не кидаццо )

Ни в коем случае...

Во-первых, перед с++ было поставлено требование поддерживать совместимость с с (си)(sic!). Вопрос - при чем здесь delete[]?

Есть работа с памятью в стиле С. Это у нас malloc и free. Вот и совместимость с С.
Есть в стиле С++. Это new и delete. И смешивать их нельзя.

в рантайме невозможно в принципе узнать, какой тег следует приписать указателю - массив или нет

Я чуть выше дала ссылки на реализации динамических массивов в С++. К любому их них можно добавить информацию массив/не массив. Информация о числе элементов уже хранится, можно хранить и что-то еще, значит...

Алёна комментирует...

2ilya:
При использовании delete[] очевидно, что компилятор использует некий механизм, который знает сколько элементов нужно удалить, Следовательно так или иначе в рантайме всегда есть информация о том, сколько элементов содержится в массиве по данному указателю. Почему бы не ввести в язык возможность узнавать эту длину? Вместо такого удобства приходится постоянно передавать массивы вместе с указанием кол-ва элементов в них.

Теоретически получается, что да, это возможно. Но меня этот момент волнует меньше, потому что это чисто сишная работа с массивами получается. Можно использовать std::vector или что там больше для данной задачи подходит и с размером не мучаться.

2sse:
опять же, из за невозможности это сделать в рантайме.

Ээээ, ну почему же, компилятор-то как раз знает размер массива, причем именно в рантайме.

Поэтому все остается на совести программиста. В угоду производительности.

Ну вот скорее по этой причине...

Алёна комментирует...

2Анонимный:
насколько я помню отличие delete[] от delete в вызове пачки деструкторов вместо одного, а для случая стандартных типов данных, где нет деструкторов, никакой разницы между delete и delete[] нету - никаких утечек небудет...

Нет, различие не только в этом. Массив в начале может содержать некоторую дополнительную информацию, и если его начать удалять обычным delete'ом, то получится удаление не пойми какого адреса. По Стандарту - udefined behavior.
Подробнее здесь: Can I drop the [] when deleteing array of some built-in type (char, int, etc)?

Алёна комментирует...

2Yuriy Volkov:
delete[] там был, но компилятор не соответствовал стандарту так как в те времена еще стандарта не было (1992 год). namespace, std, string - нету. Там много чего вообще нет. Так что нечему тут удивляться ;-)

Ну как же так, должны же были быть хотя бы какие-нибудь соглашения, назидания Страуструпа.
Ну хоть что-нибудь...
:-)

Prokrust комментирует...

В C++ FAQ Lite есть описание двух алгоритмов: over-allocation и associative array
В первом случае delete для массива и для одного объекта должен быть разным по определению, если конечно не считать один объект массивом из одного элемента (+4 байта при этом).
Во втором случае можно сделать delete одинаковыми, но зато этот способ намного медленнее.
ЗЫ. Страуструп делая С++ руководствовался скоростью выполнения. Все что могло замедлить выполнение было отброшено. И конечно язык строился так чтобы компактнее хранить данные.

Анонимный комментирует...

А я просто запускаю valgrind и он говорит где я что забыл.

zorgg комментирует...

Но экономия получается уж очень несущественная. Ну разве что для мобильных устройств она может оказаться значимой.

И это пишет человек, не понаслышке знающий о программировании игр =)

Я знаю людей, которые в статьях 2006-го года (т.е., не 86-го) пишут следующее:

[...]As this sums up to 12 bytes in total, all nodes are aligned on four-byte boundaries. This allows one to use the lower two bits of the children-pointer to indicate the axis (00: x, 01: y, 10: z) or a leaf (case 11).

И, в общем-то, абсолютно правы.

ilya комментирует...

Анонимный пишет...
А я просто запускаю valgrind и он говорит где я что забыл.

Это типа, лучше, чем использовать идиомы языка, которые все делают автоматически и никогда ничего "не забывают" =)? А вот у меня пара проектов с валгриндом часов пять будет работать. =)

У нас на работе вообще никто про смартпойнтеры не слышал. И обычные массивы были в почете до недавнего времени.

2 Zorgg
Я знаю людей, которые в статьях 2006-го года (т.е., не 86-го) пишут следующее:
[...]As this sums up to 12 bytes in total, all nodes are aligned on four-byte boundaries. This allows one to use the lower two bits of the children-pointer to indicate the axis (00: x, 01: y, 10: z) or a leaf (case 11).


Жесть. Реально жесть. =)

Анонимный комментирует...

>А вот у меня пара проектов с валгриндом
>часов пять будет работать. =)

а зачем целый проект, обычно delete[] парный new не покидает пределов модуля,
достаточно автоматизировать запуск unit тестов под valgrind.

Хотя должен признаться, что проблема именно delete[] меня давно не беспокоила,
хотя занимаюсь встроенными решениями.

Yuriy Volkov комментирует...

2Алёна:
Ну как же так, должны же были быть хотя бы какие-нибудь соглашения, назидания Страуструпа.
Ну хоть что-нибудь...
:-)

да, в 1986 году вышло первое издание книги Страуструпа, в 1991 - второе. Еще был CFront (компилятор С++, который написал Страуструп). Стандартной библиотеки шаблонов для С++ тогда еще попросту не существовало. Хотя cin/cout компилятором поддерживались, но они изначально не были частью STL для C++. Чуть подробнее про историю STL можно почитать здесь.
В силу этих обстоятельств каждый производитель компилятора мог по своему толковать книги и рекомендации. В частности то, как работает delete. Я рад, что этот период давно пройден и глубоко сожалею, что в некоторый университетах до сих пор для обучения используют BC++ 3.1

sse комментирует...

2Алёна
>>Ээээ, ну почему же, компилятор-то
>> как раз знает размер массива,
>> причем именно в рантайме.

В run-time он не может знать, потому что его удел - это время compile-time. Он только организует код в compile-time так, что может потом в run-time выяснить, что это за объект, обратившись к менеджеру памяти. А лишний код - дополнительное место, и, самое главное, _время_.

Алёна комментирует...

2Prokrust:

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

Угу, ну да, плюс сколько-то там байт будет.

2zorgg:
И это пишет человек, не понаслышке знающий о программировании игр =)

Я знаю людей, которые в статьях 2006-го года (т.е., не 86-го) пишут следующее:

<skip>

И, в общем-то, абсолютно правы.


Сурово.
Все, конечно, от задачи зависит... Если, к примеру, у тебя память течет по мегабайту в час, то сэкономленные несколько байт вряд ли тебя обрадуют.

2sse:
В run-time он не может знать, потому что его удел - это время compile-time. Он только организует код в compile-time так, что может потом в run-time выяснить, что это за объект, обратившись к менеджеру памяти.

Угу, ок.

А лишний код - дополнительное место, и, самое главное, _время_.

Это да, безусловно.

gravizappa комментирует...

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

Алёна комментирует...

Не поленилась скачать Borland C++ 3.1, чтобы проверить, не удалит ли там delete массив. Не удалил.

Честное удаление массива из трех элементов через delete[]

CBase::CBase
CBase::CBase
CBase::CBase
CBase::~CBase
CBase::~CBase
CBase::~CBase

Нечестное удаление массива из трех элементов через delete

CBase::CBase
CBase::CBase
CBase::CBase
CBase::~CBase
Null pointer assignment

pesec комментирует...

Не убедительно, Алёна. Вам уже рассказали, что информацию, массив ли это или один объект, нужно где-то хранить. И платить за это! А Строустрап говорил: если не используете -- не плАтите.

Алёна комментирует...

2pesec:
Не убедительно, Алёна. Вам уже рассказали, что информацию, массив ли это или один объект, нужно где-то хранить.

Так она уже есть.

И платить за это! А Строустрап говорил: если не используете -- не плАтите.

Если нужна какая-нибудь информация, кроме той, которая есть - можно сделать какой-нибудь волшебный ключик. Как с RTTI.

Rinat Galiulin комментирует...

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

Fallout комментирует...

Есть стандарт там всё сказано....

char *a = new char;
char *b = new [10];

delete[] a; поведение не определено
delete b; поведение не определено

соотвественно как сказано выше если реализовать не так как в стандарте то будет не соответствие стандарту :) кстати это же не один неопределенный случай.... а про внутренности тут уже всё от автора компилятора зависит.... так же как в стандарте не описана реализация внутренностей класса так и компиляторы делают её по своему например g++ и msvc++ ^)

Tonal комментирует...

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

Останутся проблемы несовместимости с С, где понятия смешаны.
С другой стороны, можно ввести новую типизацию параллельно старой - как сделано с приведениями, например.

P.S. Я на вскидку не смог придумать алгоритм, где бы эти типы нельзя бы было тривиально разделить. :-)

Анонимный комментирует...

Хм... ведь известен размер выделенной области памяти и размер удаляемого типа данных, соответсвенно можно определить кол-во элементов в массиве. Получается, что в delete[] нет необходимости. Или я не прав?

Алёна комментирует...

2Анонимный:
Хм... ведь известен размер выделенной области памяти и размер удаляемого типа данных, соответсвенно можно определить кол-во элементов в массиве. Получается, что в delete[] нет необходимости. Или я не прав?

Дык эта, о том и пост. :-)

Oleg комментирует...

А так программисту приходится всегда помнить, что именно вызывать для освобождения памяти: delete или delete[]. Зачем - не очень понятно. Информация о том, сколько именно элементов в массиве все равно где-то да хранится. Почему бы компилятору вообще не освободить программиста от каких-либо запоминаний?

Очень странный вопрос для опытного программиста на С++ :) Его задают на собеседовании, что бы отсеять новичков.

Что бы понять разницу предлагаю прогнать пару раз такую программу:


#include <stdio.h>

class Test
{
public:

~Test()
{
puts("~");
}
};

int main(int, char**)
{
Test *a = new Test[2];

// пробуем два варианта
// delete a;
delete[] a;

return(0);
}

Анонимный комментирует...

Хм.. мне тоже странно. Почему бы компилятору не привести delete к delete[]? Ведь по сути там только надо добавить количество элементов в структуру, создаваемую new. А если это нельзя, то почему бы не выдавать предупреждение если используется delete вместо delete[]?

bombazook комментирует...

Программист я не достаточно опытный, конечно... Но всё же работая уже над своим вторым движком и менеджером памяти к нему, до сих пор ловлю себя на мысли с таким "дурацким" вопросом.

2Алёна: Спасибо за ведение такого замечательного блога - не первый раз обнаруживаю обсуждение интересных тем.=)

Анонимный комментирует...

Случайно наткнулся на эту ветку. И очень удивился, что delete [] мешает кому-то жить. Скажу так, что во-первых delete [] - вносит ясность в код, что удаляем именно массив, а не что-то еще. Т.е. создал что-то c [] незабудь, это что-то [] освободить. Когда код будет читать кто-то еще запись без скобок логически может быть неясна!. Второе, вообще не понял зачем городить какие-то границы массива. Не забываем, что массив это указатель на область памяти и всё... Для всяческих приятных извратов есть vector, list и т.д. Т.е. я хочу сказать, что реализация массива в С++ это самый низкий уровень, и программисту предоставляется ВЫБОР: или оптимизация и колбасня с указателями или простота вектора, списка и т.п.

Ух,
Я за простой и понятный код!


P.S.
А есть еще тип char* ... который окaнчивается /0 - и чтобы выбрать элементы нам достаточно знать только это, а не количество символов.... (это к слову о границах массива)


А также можно написать:

char *a=new char [20];
strcpy (a,"Hello World");
a[5]='_';


//Цена разницы синтаксиса
delete a; //Освободили только первый элемент -- утечка памяти
delete [] a; //Освободили все

//Отношение к памяти должно быть бережное несмотря на ее количество.

//Тот кто считает, что освобождать память не нужно - не должен писать программы на С и С++. Для этого есть Basic, Java, и .т.п.

Алёна комментирует...

2Анонимный:

Т.е. создал что-то c [] незабудь,
это что-то [] освободить.


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

Aleksandr комментирует...

По-моему в одной из публикаций Страуструпа, было написано, что оператор освобождения памяти для массивов должен был выглядеть так:

delete[n] ptr; // ptr указывает на массив из n элементов

В этом случае компилятору не нужно было сохранять информацию о кол-ве элементов. А уже потом его решили сократить до:

delete[] prt;

Tonal комментирует...

Для блока памяти известен его размер.
При удалении массива компилятор знает тип его элемента.
Поэтому количество элементов он очень просто вычислит.
Стало быть, delete[n] - излишен.

А вот различить на что указывает указатель - на массив или на один элемент компилятор не может - синтаксически ситуации не различимы.
Поэтому без деления на delete и delete[] не получается.

Alex комментирует...

Наконец, вы меня успокоили, что можно писать delete везде без сомнительных скобок. Почитал в Интернете, что по этому поводу пишут. Как я выяснил вся разница в (возможно) производительности. Также есть такие несущественные детали как вызов конструкторов для отдельных объектов и (НЕПОНЯТНО!!!) кому нужная перегрузка операторов new/new[] и delete/delete[].

Я предпочитаю никогда не писать код типа new Object [100]; Я всегда использую массивы указателей m = new *Object[100]. И потом for (...) m[i] = new Object; Указатели не имеют деструкторов, а удалять объекты гораздо понятнее через for (...) delete m[i]; delete m; где Object *m[100]; например.

delete[] очень сложно всегда помнить. Что касается векторов, то у меня в проектах они реализованы в наших библиотеках, а не в std, т.к. на 20 ОС, которые нужно поддерживать явно нельзя рассчитывать на отсутствие багов в той или иной библиотеке std.

Alena комментирует...

Alex:

Наконец, вы меня успокоили, что можно писать delete везде без сомнительных скобок

Нет, такого я не писала. delete[] нужно использовать при работе с массивами.

Alex комментирует...

Я не конкретно про вас Алена. Я про Интернет. Он меня успокоил, в т.ч. и этот блог.

Tonal комментирует...

Наконец, вы меня успокоили, что можно писать delete везде без сомнительных скобок
Нужно не "успокаиваться интернетом" а разбираться с непонятными вопросами.
Тогда вместо глупости типа "delete везде без сомнительных скобок" просто будешь правильно расставлять скобки. :)

Анонимный комментирует...

Ну то, что нельзя массив удалять delet'ом, как бы выяснили. Возникает другой вопрос: а можно ли всегда и во всем использовать delete []? Т.е. чего ждать если какой нибудь

int *p=new int(5);
//удалить так
delete [] p; //?

Анонимный комментирует...

undefind behaviour ждать

tonal комментирует...

а можно ли всегда и во всем использовать delete []?
При использовании delete[] для "всего" возникают 2 неоднозначности:
1. delete[] не работает с динамическими типами - всегда только статмические. Т. е. при попытке удалить экземпляр порождённого класса через указатель на базовый, вызовется деструктор базового, т. к. С++ не предоставляет полиморфных массивов.
2. Количество разрушаемых элементов нужно как-то узнавать. Но если использовать один и тот же оператор и для единичных объектов, и для массивов, то придётся делать одинаковый механизм для тех и других.
Пусть, например, мы решили сохранять количество элементов перед массивом (по отрицательному смещению) - тогда придётся так же поступать и для единичного объекта. Т. е. перед каждым объектом сохранять количество - 1. :)
Или другой вариант - можно узнать количество, если поделить нацело размер занимаемого блока на размер элемента. Но что делать если это экземпляр порожденного класса, который занимает совсем другой размер чем базовый?

В общем причина непоняток в том, что объект и массив объектов - это таки разные типы, но в языках С/С++ указатель на объект и указатель на массив неявно приводятся, и работая с динамическими массивами, мы вынуждены оперировать указателем на первый элемент.
Т. е. компилятор просто не можето отличить идёт ли работа с динамическим массивам или с отдельным элементом. А вынесение этого в рантайм неизбежно приведёт к пенальти по размеру или скорости при работе с динамической памятью...

safinaskar комментирует...

ВНИМАНИЕ!!! delete [] вызывает деструкторы для каждого элемента массива, а delete вызывает лишь один деструктор, то есть:
T[5] a;
delete a;
Конструктор вызван 5 раз, а деструктор 1. Мне это сказал препод по проге, так что инфа 100%

Анонимный комментирует...

На самом деле дела обстоят так.
delete и delete[] - это совсем разные вещи. delete вместе с new - это выделение памяти, грубо говоря - это контролируемый и перегружаемый аналог malloc/free / HeapAlloc/HeapFree и любой другой кучи (просто примеры, конечно).
А delete [] делает совсем другое. Он берёт кол-во элементов массива, хранящееся в предыдущих (!) 4 байтах (в общем случае, размер size_t) перед экземпляром первого класса и крутит цикл, в котором вызывает DTOR-ы объектов столько раз, сколько там указано. Ну а new[], собственно, перед вызовом конструкторов кладет туда это кол-во. И размер блока кучи тут совершенно ни при чем. Собственно, всё это можно проверить, скомпилировав такой код:

std::string a1;
std::string* a2 = &a1;
delete [] a2;

и посмотрев его в дизасме (std::string тут взят для примера, разглядеть всю эту логику намного проще с минимальной генерацией 'поддерживающего' кода - т.е. с отключенными /defaultlib и т.д.)

Анонимный комментирует...

Кстати, delete[] и delete различаются только для типов, имеющих деструктор. К примеру, для встроеных типов никаких sizeof(size_t) байт перед первым экземпляром в блоке выделяться под хранение кол-во объектов не будет.
Это всё только наблюдения, аподиктически ничего не утверждается, к примеру, я чисто с потолка взял sizeof(size_t) как самое очевидное для описание размера в 4 байта на моей виндовой платформе. Мейби, в стандарте где-то и указаны такие детали поведения delete и delete[].

Анонимный комментирует...

int *p_var = new int;
int *p_array = new int[50];

delete[] p_array;
delete p_var;