W jaki sposób delete [] „zna” rozmiar tablicy argumentów?

250
Foo* set = new Foo[100];
// ...
delete [] set;

Nie przekraczasz granic tablicy delete[]. Ale gdzie są przechowywane te informacje? Czy to jest znormalizowane?

VolkerK
źródło
sourceforge.net/projects/fastmm jest open source i zastępuje menedżera pamięci. Tutaj możesz dowiedzieć się, jak działa zarządzanie pamięcią i skąd pochodzą informacje dotyczące przydzielania i usuwania pamięci.
1
Zauważ, że FastMM jest specyficzny tylko dla kompilatorów Delphi / C ++ Builder, nie jest to menedżer pamięci ogólnego przeznaczenia dla C ++. Nie jest nawet napisany w C ++.
Remy Lebeau,

Odpowiedzi:

181

Kiedy przydzielasz pamięć na stercie, twój alokator będzie śledził, ile pamięci przydzieliłeś. Zazwyczaj jest to przechowywane w segmencie „head” tuż przed przydzieloną pamięcią. W ten sposób, gdy nadejdzie czas na zwolnienie pamięci, de-alokator wie dokładnie, ile pamięci do zwolnienia.

Peter Kühne
źródło
4
Zauważ, że dotyczy to tylko przydziałów tablic w C ++. Wszystkie inne przydziały zależą od wielkości typu. Niektóre biblioteki przechowują wszystkie wielkości alokacji, zwykle tylko w trybie debugowania.
Zan Lynx
97
Nie ma absolutnie żadnego powodu, dla którego programiści nie powinni mieć tych informacji. Mogę przekazać wskaźnik do funkcji i zwolnić go, ale aby sam uzyskać rozmiar w tej samej funkcji, muszę przekazać dodatkowy parametr. Czy to ma jakiś sens?
Mark Ruzon,
26
@ Mark, ma to niewielki sens, ponieważ teoretycznie zwalnia alokatora, aby zawsze przechowywał rozmiar przydzielonego bloku (który może różnić się od rozmiaru żądanego bloku). Niektóre projekty alokatorów mogą potrzebować tych informacji do własnych celów lub mogą nie być na tyle skomplikowane, aby wykorzystywać informacje o typie do śledzenia wielkości przydziałów sterty innej niż tablica itp. Zmuszanie alokatora do przechowywania żądanego rozmiaru (abyś nie konieczność samodzielnego przekazania rozmiaru tablicy) może być niewielkim obciążeniem, ale może mieć wpływ na wydajność możliwych do wyobrażenia projektów alokatorów.
Doug McClean
33
Przepraszamy, ale ta odpowiedź nie ma sensu. To, co opisał QuantumPete, to w zasadzie „Skąd freewiadomo, ile pamięci należy zwolnić”. Tak, rozmiar bloku pamięci jest przechowywany „gdzieś” przez malloc(zwykle w samym bloku), więc o tym freewie. Jednak new[]/ delete[]to inna historia. Te ostatnie działają w zasadzie na malloc/ free. new[]przechowuje również liczbę elementów, które utworzył w bloku pamięci (niezależnie od malloc), aby później delete[]móc pobrać ten numer i użyć go do wywołania odpowiedniej liczby destruktorów.
AnT
26
To znaczy fizycznie dwa liczniki są przechowywane w bloku: rozmiar bloku (według malloc) i liczba elementów (według new[]). Zauważ, że tego pierwszego nie można użyć do obliczenia drugiego, ponieważ w ogólnym przypadku wielkość bloku pamięci może być większa niż naprawdę konieczna dla tablicy żądanego rozmiaru. Zauważ też, że licznik elementów tablicy jest potrzebny tylko dla typów z nietrywialnym destruktorem. W przypadku typów z trywialnym destruktorem licznik nie jest przechowywany przez new[]i, oczywiście, nie jest pobierany przez delete[].
AnT
23

JEDNYM z podejść do kompilatorów jest przydzielenie nieco więcej pamięci i zapisanie liczby elementów w elemencie głównym.

Przykład, jak można to zrobić:

Tutaj

int* i = new int[4];

kompilator przydzieli sizeof(int)*5bajty.

int *temp = malloc(sizeof(int)*5)

Będzie przechowywać „4” w pierwszych sizeof(int)bajtach

*temp = 4;

i nastaw i

i = temp + 1;

Więc ipunkty do tablicy 4 elementów, a nie 5.

I usunięcie

delete[] i;

będą przetwarzane w następujący sposób:

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements
... that are stored in temp + 1, temp + 2, ... temp + 4 if needed
free (temp)
Avt
źródło
9

Informacje nie są znormalizowane. Jednak na platformach, nad którymi pracowałem, ta informacja jest przechowywana w pamięci tuż przed pierwszym elementem. Dlatego możesz teoretycznie uzyskać do niego dostęp i sprawdzić go, jednak nie jest to tego warte.

Również dlatego musisz użyć delete [], kiedy przydzielisz pamięć nowemu [], ponieważ tablicowa wersja delete wie, że (i gdzie) musi szukać zwolnienia odpowiedniej ilości pamięci - i wywołać odpowiednią liczbę destruktorów dla obiektów.

Daemin
źródło
5

Zasadniczo jest umieszczony w pamięci jako:

[informacje] [mem, o który prosiłeś ...]

Gdzie info to struktura używana przez kompilator do przechowywania ilości przydzielonej pamięci, a co nie.

Jest to jednak zależne od implementacji.

Francisco Soto
źródło
4

To nie jest coś, co jest w specyfikacji - zależy od implementacji.

jeffm
źródło
3

W standardzie C ++ jest zdefiniowany jako specyficzny dla kompilatora. Co oznacza magię kompilatora. Może zerwać z nietrywialnymi ograniczeniami wyrównania na co najmniej jednej głównej platformie.

Możesz pomyśleć o możliwych implementacjach, zdając sobie sprawę, że delete[]jest zdefiniowane tylko dla wskaźników zwracanych przez new[], które mogą nie być tym samym wskaźnikiem, co zwracane przez operator new[]. Jedną z implementacji na wolności jest przechowywanie liczby tablic w pierwszej int zwracanej przez operator new[]i new[]zwracanie wskaźnika przesuniętego poza to. (Dlatego nietrywialne dopasowania mogą się złamać new[].)

Pamiętaj, że operator new[]/operator delete[]! = new[]/delete[].

Ponadto jest to prostopadłe do tego, w jaki sposób C zna wielkość przydzielonej pamięci malloc.

MSN
źródło
2

Ponieważ tablica, która ma zostać „usunięta”, powinna zostać utworzona przy użyciu jednego operatora „nowego”. „Nowa” operacja powinna była umieścić tę informację na stercie. W przeciwnym razie, skąd dodatkowe zastosowania nowych wiedziałby, gdzie kończy się kupa?

Joel Coehoorn
źródło
0

Nie jest znormalizowany. W środowisku wykonawczym Microsoftu nowy operator używa malloc (), a operator usuwania używa free (). Tak więc, w tym ustawieniu twoje pytanie jest równoważne z następującym: Skąd free () zna rozmiar bloku?

Za kulisami jest trochę księgowości, tj. W środowisku wykonawczym C.

Andre
źródło
5
Nie prawda. Zanim zadzwonisz za darmo, delete [] musi najpierw wywołać destruktory. Do tego wiedząc, całkowity rozmiar przydziałów nie wystarczy. W rzeczywistości nowe [] i delete [] działają inaczej dla zwykłych i zniszczonych typów w VC ++.
Suma
0

Jest to bardziej interesujący problem, niż mogłoby się początkowo wydawać. Ta odpowiedź dotyczy jednej możliwej implementacji.

Po pierwsze, chociaż na pewnym poziomie twój system musi wiedzieć, jak „zwolnić” blok pamięci, leżący u jego podstaw malloc / free (który zazwyczaj nazywają new / delete / new [] / delete []) nie zawsze pamięta dokładnie, ile pamięci jeśli poprosisz, może zostać zaokrąglony w górę (na przykład, gdy przekroczysz 4K, często jest zaokrąglany w górę do następnego bloku wielkości 4K).

Dlatego nawet jeśli można uzyskać rozmiar bloku pamięci, nie mówi nam to, ile wartości znajduje się w nowej [] edytowanej pamięci, ponieważ może być mniejsza. Dlatego musimy przechowywać dodatkową liczbę całkowitą, która mówi nam, ile jest wartości.

Z WYJĄTKIEM, jeśli budowany typ nie ma destruktora, to delete [] nie musi nic robić poza zwolnieniem bloku pamięci, a zatem nie musi niczego przechowywać!

Chris Jefferson
źródło