Является ли delete [] равным delete? - PullRequest
42 голосов
/ 12 октября 2009
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 

Ответы [ 6 ]

146 голосов
/ 12 октября 2009

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

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

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

13 голосов
/ 24 сентября 2013

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

Тест 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
7 голосов
/ 12 октября 2009

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

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

Подробнее см. . .

4 голосов
/ 11 августа 2011

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

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

3 голосов
/ 12 октября 2009

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

Почему?

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

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

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

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

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

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

0 голосов
/ 12 октября 2009

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

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

EDIT:

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

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

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

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

EDIT2:

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

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