Удалить [], равное удалению?

IP_ADAPTER_INFO *ptr=new IP_ADAPTER_INFO[100];

если я освобожу, используя

delete ptr;

это приведет к утечке памяти, если не тогда, почему?

Это код разборки, сгенерированный VS2005

; delete ptr;
0041351D  mov         eax,dword ptr [ptr] 
00413520  mov         dword ptr [ebp-0ECh],eax 
00413526  mov         ecx,dword ptr [ebp-0ECh] 
0041352C  push        ecx  
0041352D  call        operator delete (4111DBh) 
00413532  add         esp,4 

; delete []ptr;
00413535  mov         eax,dword ptr [ptr] 
00413538  mov         dword ptr [ebp-0E0h],eax 
0041353E  mov         ecx,dword ptr [ebp-0E0h] 
00413544  push        ecx  
00413545  call        operator delete[] (4111E5h) 
0041354A  add         esp,4 

Ответы

Ответ 1

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

Все это и бесконечное количество других возможностей помещаются в один термин: Undefined поведение:

Просто держитесь подальше от него.

Ответ 2

Просто иллюстрация некоторых действий "undefined" для некоторых ОС и компиляторов. Надеюсь, что это может быть полезно для людей, чтобы отлаживать их код.

Тест 1

#include <iostream>
using namespace std;
int main()
{
  int *p = new int[5];
  cout << "pass" << endl;
  delete p;
  return 0;
}

Тест 2

#include <iostream>
using namespace std;
int main()
{
  int *p = new int;
  cout << "pass" << endl;
  delete[] p;
  return 0;
}

Тест 3

#include <iostream>
using namespace std;
struct C {
  C() { cout << "construct" << endl; }
  ~C() { cout << "destroy" << endl; }
};

int main()
{
  C *p = new C[5];
  cout << "pass" << endl;
  delete p;
  return 0;
}

Тест 4

#include <iostream>
using namespace std;
struct C {
  C() { cout << "construct" << endl; }
  ~C() { cout << "destroy" << endl; }
};

int main()
{
  C *p = new C;
  cout << "pass" << endl;
  delete[] p;
  return 0;
}
  • Windows 7 x86, msvc 2010. Компиляция с настройками по умолчанию, то есть обработчик исключений.

Тест 1

pass

Тест 2

pass

Тест 3

construct
construct
construct
construct
construct
pass
destroy
# Then, pop up crash msg

Тест 4

construct
pass
destroy
destroy
destroy
destroy
destroy
destroy
destroy
... # It never stop until CTRL+C
  • Mac OS X 10.8.5, llvm-gcc 4.2 или gcc-4.8 генерируют один и тот же вывод

Тест 1

pass

Тест 2

pass

Тест 3

construct
construct
construct
construct
construct
pass
destroy
a.out(71111) malloc: *** error for object 0x7f99c94000e8: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
zsh: abort      ./a.out

Тест 4

construct
pass
a.out(71035) malloc: *** error for object 0x7f83c14000d8: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
zsh: abort      ./a.out
  • Ubuntu 12.04, AMD64, gcc 4.7

Тест 1

pass

Тест 2

pass

Тест 3

construct
construct
construct
construct
construct
*** glibc detected *** ./a.out: munmap_chunk(): invalid pointer: 0x0000000001f10018 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7fe81d878b96]
./a.out[0x400a5b]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7fe81d81b76d]
./a.out[0x4008d9]
======= Memory map: ========
....
zsh: abort (core dumped)  ./a.out

Тест 4

construct
destroy
destroy
destroy
destroy
destroy
destroy
destroy
destroy
...
destroy
destroy
*** glibc detected *** ./a.out: free(): invalid pointer: 0x00000000016f6008 ***
======= Backtrace: =========
/lib/x86_64-linux-gnu/libc.so.6(+0x7eb96)[0x7fa9001fab96]
./a.out[0x400a18]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xed)[0x7fa90019d76d]
./a.out[0x4008d9]
======= Memory map: ========
...
zsh: abort (core dumped)  ./a.out

Ответ 3

Он обычно не течет, потому что в случае деструкторов POD тривиальны, и нет необходимости их вызывать, поэтому delete просто освобождает память, занятую массивом. Для освобождения памяти требуется только значение указателя, поэтому оно будет возвращено в кучу. Массив принимает смежный блок памяти, и поэтому освобождение может быть успешным, как если бы это было освобождение одного элемента.

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

Подробнее см. этот ответ.

Ответ 4

Если A указывает на массив, который был назначен через новый T [n], вы должны удалить его с помощью delete [] A.

Почему?

Разница между delete и delete [] проста: первый уничтожает скалярный объект, а последний уничтожает массив.

Подробнее здесь и здесь.

ОБНОВЛЕНИЕ 1 (неверно):

Если вы используете delete (а не delete []) при распределении с помощью нового T [n], только первый элемент освобождается, а другие не деструктурированы, что приведет к утечке памяти. Зачем? Так Бьярне Страуструп и другие разработали язык. И это не зависит от компилятора. Если один компилятор освобождает другой способ, он просто не соответствует стандарту. Язык программирования С++, главы 6.2.6.2 и 19.4.5.

ОБНОВЛЕНИЕ 2 (правильно):

Я допускаю свою ошибку в отношении поведения использования оператора delete при распределении с новым T [n]. Читая упомянутый документ, я не нашел точное описание, поэтому я предполагаю, что в этой ситуации поведение будет undefined и будет отличаться от компилятора к компилятору. AFAIK, MSVC-компилятор, например, будет генерировать другой код, чем GCC. Пожалуйста, игнорируйте UPDATE 1.

Ответ 5

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

удалить []:   вызывает в своем массиве соответствующие деструкторы для каждого элемента (если необходимо),   затем освобождает фрагмент памяти

Ответ 6

Для массива POD он будет не протекать (с большинством компиляторов). Например, MSVC генерирует идентичный код удалить и удалить [] для массива POD strong > .

Лично я считаю, что C/С++ может быть без оператора delete []. Компилятор знает размер объекта, а выделенный размер памяти известен во время выполнения, поэтому очень просто знать массив указателей или нет и правильно распоряжаться памятью.

EDIT:

ОК, ребята. Можете ли вы протестировать свой компилятор и сказать, что он протекает?

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

1. call vector destructor for an object
2. actual free memory

Что такое деструктор для POD? Ничего! Таким образом, вызов удалить для массива POD не будет протекать! Даже если он нарушает стандарт. Даже если это не рекомендуется.

EDIT2:

Это код разборки, сгенерированный VS2008:

operator delete[]:
78583BC3  mov         edi,edi 
78583BC5  push        ebp  
78583BC6  mov         ebp,esp 
78583BC8  pop         ebp  
78583BC9  jmp         operator delete (78583BA3h)