Когда вы пишете p = new T[N]
в своем коде, компилятор генерирует код, который вызывает operator new[]
, чтобы выделить достаточно памяти для N
объектов типа T
плюс любую необходимую для учета информацию. Когда вы впоследствии вызываете delete[] p
, компилятор вызывает деструктор для каждого из N
элементов в массиве, на который указывает p
, затем вызывает operator delete[]
, чтобы освободить память, полученную из operator new[]
.
Но N
не всегда имеет одинаковое значение. Вы можете сделать p = new T[3]; delete[] p; p = new T[4]; delete[] p;
, и каждый из двух удалений будет запускать разное количество деструкторов.
Чтобы вызвать правильное количество деструкторов, где-то должна быть записка, в которой записано, сколько объектов в массиве указывает на p
. Эта заметка обычно хранится в той части памяти, которую компилятор получил от вызова operator new[]
. Вот почему ему нужно дополнительное место.
Это типичная реализация в наши дни, но компилятор не обязан делать это таким образом. Например, по крайней мере одна ранняя реализация хранила отдельную таблицу значений указателей и количества деструкторов.
Кроме того, многие реализации не используют дополнительные издержки для типов, которые не имеют деструкторов. Так что int *p = new int[3]
просто позвонит operator new(3*sizeof(int)
, а delete[] p
просто позвонит operator delete(p)
.