2009-03-18 7 views

Odpowiedz

14

Obiekty utworzone za pomocą new[] muszą używać delete[]. Używanie delete jest niezdefiniowane w tablicach.

Dzięki malloc i bezpłatnym masz prostszą sytuację. Istnieje tylko jedna funkcja, która zwalnia przydzielone dane, nie ma też pojęcia o nazwie destruktora. Dezorientacja po prostu pojawia się, ponieważ delete[] i usuwanie wyglądają podobnie. W rzeczywistości są to 2 całkowicie różne funkcje.

Użycie polecenia delete nie wywołuje prawidłowej funkcji usuwania pamięci. Powinien zadzwonić pod numer delete[](void*), ale zamiast tego wywołuje delete(void*). Z tego powodu nie można polegać na wykorzystaniu pamięci przydzielonej dla delete z new[]

See this C++ FAQ

[16,13] Czy mogę upuścić [] gdy deleteing tablica jakiś wbudowany typ (char, int itp.)?

Nie!

Czasami programiści że [] w delete[] p istnieje tylko tak kompilator wezwie odpowiednie destruktorów dla wszystkich elementów w tablicy . Z powodu tego rozumowania, zakładają, że tablica jakimś wbudowanym typu, takie jak char lub int może być delete d bez []. Np zakładają następujące Poprawny kod:

void userCode(int n) { 
    char* p = new char[n]; 
    ... 
    delete p; // ← ERROR! Should be delete[] p ! 
} 

Ale powyższy kod jest niewłaściwy, a może spowodować katastrofę w czasie wykonywania. W szczególności kod, który nazywa się na delete p jest operator delete(void*), ale kod, który nazywa się na delete[] p jest operator delete[](void*). Zachowanie domyślne dla tego ostatniego jest połączenie pierwszego, jednak użytkownicy mogą zastąpić drugi z innym zachowania ( w którym to przypadku zwykle również wymiany odpowiadającej nowy kod w operatora new[](size_t)). Gdyby otrzymuje kod delete[] więc nie była zgodna ze kasowanie kodów , a nazywa się złego (czyli jeśli powiedział delete p raczej niż delete[] p), może skończyć się z katastrofy w czasie wykonywania .

Dlaczego delete[] istnieć w pierwszej kolejności?

czy robisz x lub y:

char * x = new char[100]; 
char * y = new char; 

Oba są przechowywane w char * wpisywanych zmiennych.

Myślę, że powód decyzji delete i delete[] idzie w parze z długą listą decyzji, które przemawiają za efektywnością w C++. Jest tak, że nie ma wymuszonej ceny, aby sprawdzić, ile trzeba usunąć dla zwykłej operacji usuwania.

mający 2 new i new[] tylko wydaje się logiczne mają delete i delete[] i tak dla symetrii.

+0

Być może możesz dodać do odpowiedzi informacje/linki dotyczące nadpisania różnych nowych/usunąć, aby odpowiedzieć na późniejsze pytanie. –

+0

@Brian Z ciekawości: dlaczego potrzeba rozróżnienia między tymi dwoma? W jaki sposób, na początku, istnieje delete []? – FreeMemory

11

Różnica polega na tym, że delete usunie tylko cały zakres pamięci, ale wywoła tylko destruktor dla 1 obiektu. delete[] spowoduje usunięcie pamięci i wywołanie destruktora dla każdego obiektu. Jeśli nie używasz delete[] dla tablic, to tylko kwestia czasu, zanim wprowadzisz wyciek zasobów do swojej aplikacji.

EDIT Aktualizacja

Zgodnie z normą, przekazując obiekt przyznaną z new[] do delete jest niezdefiniowany. Zachowanie prawdopodobnie będzie działać tak, jak to opisałem.

+0

Dlaczego więc dobrą praktyką jest usuwanie tablic znaków [] char? Tylko po to, aby zachować nawyk? –

+0

Ummm, jestem całkiem pewien, że wywołanie delete na wyniku z nowego [] (lub na odwrót) jest niezdefiniowanym zachowaniem. Może to prowadzić do znacznie gorszego niż tylko wyciek pamięci. – derobert

+0

@derobert, nie jestem w 100% pewny, czy jest zdefiniowany czy nie, ale z pewnością tworzy złe zachowanie. – JaredPar

-1

Kiedy używasz nowego [] do przydzielenia tablicy, faktycznie mówisz C++ o wielkości tablicy. Kiedy używasz malloc, zamiast tego mówisz, ile pamięci zostało przydzielone. W pierwszym przypadku uwolnienie w oparciu o rozmiar tablicy nie miałoby sensu. W tym przypadku tak jest. Ale ponieważ nie ma różnicy pomiędzy wskaźnikiem dla tablicy a dla pojedynczego obiektu, potrzebna jest osobna funkcja.

0

nowe i usuwane są różne od malloc i wolne w tym malloc i darmowe tylko przydzielić i zwolnić pamięć; nie nazywają oni lekarzy ani lekarzy.

3

Powodem tego wymogu jest historia, a ponieważ new type i new type [size] zwracają różne rzeczy, które wymagają innego sprzątania.

rozważyć ten kod

Foo* oneEntry = new Foo; 
Foo* tenEntries = new Foo[10]; 

Są zarówno zwrócić Foo * wskaźnik, różnica jest drugie połączenie spowoduje konstruktora Foo miano 10x i 10x nie jest grubsza tyle pamięci.

Teraz chcesz uwolnić swoje obiekty.

Dla pojedynczego obiektu można nazwać usuwanie - np. delete oneEntry. To wywołuje destruktor obiektów i zwalnia pamięć.

Ale tu jest problem - oneEntry i tenEntries są po prostu wskaźnikami Foo. Kompilator nie ma pojęcia, czy wskazują na jeden, dziesięć, czy tysiąc elementów.

Podczas korzystania ze specjalnej składni delete [].Mówi to kompilatorowi: "to jest tablica obiektów, oblicz liczbę, a następnie zniszcz wszystkie".

To, co naprawdę się dzieje, to, że kompilator przechowuje w tajemnicy "rozmiar" gdzie indziej. Kiedy wywołasz delete [], wie, że ta sekretna wartość istnieje, więc może dowiedzieć się, ile obiektów znajduje się w tym bloku pamięci i je zniszczyć.

Pytanie, które można następnie zadać, brzmi: "dlaczego kompilator nie zawsze przechowuje rozmiar?"

To świetne pytanie i pochodzi z początków C++. Istniało pragnienie, aby dla wbudowanych typów (char, int, float, itp.) Następujące byłyby ważne dla C++;

int* ptr = new int; 
free(ptr); 

int* ptr = (int*)malloc(sizeof(int) * someSize); 
delete ptr; 

Rozumowanie to było oczekiwanie, że ludzie dostarczają bibliotek, które zwróciło się dynamicznie przydzielone pamięci, a użytkownicy tych bibliotek miałyby żadnego sposobu dowiedzenia się, czy używać za darmo/usunąć.

Ta potrzeba kompatybilności oznaczała, że ​​rozmiar tablicy nie mógł być przechowywany jako część samej tablicy i musiał być przechowywany w innym miejscu. Z tego powodu (i pamiętajcie, że było to na początku lat 80.) zdecydowano, że będzie to książka zawierająca tylko tablice, a nie pojedyncze elementy. W związku z tym tablice wymagają specjalnej składni delete, która wyszukuje tę wartość.

Powodem, dla którego malloc/free nie ma tego problemu jest to, że po prostu zajmują się blokami pamięci i nie muszą się martwić o wywołanie konstruktorów/destruktorów.

1

Co do "dlaczego" w tytule: jednym z celów projektu C++ było to, że nie byłoby żadnych ukrytych kosztów. Rozwinięto także C++ w czasie, gdy każdy bajt pamięci wciąż liczył się znacznie bardziej niż obecnie. Projektanci języków również lubią ortogonalność: jeśli przydzielisz pamięć new[] (zamiast new), powinieneś ją zwolnić za pomocą delete[].

Nie sądzę, istnieje jakikolwiek techniczny powodem, że nie mógł new[] trzymać flagę „Jestem tablicę” w nagłówku bloku pamięci dla delete (nie więcej delete[]) spojrzeć na później.

4

Stroustrupa opowiada o przyczynach oddzielnym new/new[] i delete/ delete [] `operatorzy w "Design and Evolution of C++" w sekcji 10.3 poprzez 10.5.1:

  • 10,3 Array - Przydział omawia, że ​​chcieli, aby umożliwić przydzielanie tablic obiektów za pomocą oddzielnego schematu z alokacji pojedynczych obiektów (tj. przydzielanie macierzy z oddzielnego sklepu). Dodanie wersji tablicowych new i delete było rozwiązaniem tego problemu;
  • 10.5.1 Deallocating Arrays - omawia, w jaki sposób problem z deallocating tablic przy użyciu tylko jednego operatora delete jest to, że musi być więcej informacji niż tylko wskaźnik w celu ustalenia, czy wskaźnik wskazuje na pierwszy element array lub jeśli po prostu wskazuje na pojedynczy obiekt. Zamiast "komplikować wspólny przypadek przydzielania i deallocacji poszczególnych obiektów", operator delete[] służy do obsługi tablic. Jest to zgodne z ogólną filozofią projektowania w C++: "nie płać za to, czego nie używasz".

To, czy ta decyzja była pomyłką, czy nie, jest dyskusyjne - tak czy inaczej ma dobre argumenty, ale mamy to, co mamy.

Powiązane problemy