Skąd darmo wie, ile kosztuje?

384

W programowaniu C możesz przekazać dowolny wskaźnik, który ci się podoba, jako argument do zwolnienia, skąd on zna wielkość przydzielonej pamięci do zwolnienia? Ilekroć przekazuję wskaźnik do jakiejś funkcji, muszę również przekazać rozmiar (tj. Tablica 10 elementów musi otrzymać 10 jako parametr, aby znać rozmiar tablicy), ale nie muszę przekazywać rozmiaru do darmowa funkcja. Dlaczego nie i czy mogę korzystać z tej samej techniki we własnych funkcjach, aby uchronić się przed koniecznością korzystania z dodatkowej zmiennej długości tablicy?

Joshua Cheek
źródło
Podobne pytanie: stackoverflow.com/questions/851958/... (choć powiedziałbym, że to nie do końca duplikat)
John Carter,
System koleżeński to kolejny sposób na zrobienie tego na podstawie wskaźnika, bez narzutu w każdym bloku.
EvilTeach
Ten post dobrze to wyjaśnia: stackoverflow.com/questions/1957099/...
Zeeshan Mahmood,

Odpowiedzi:

348

Kiedy dzwonisz malloc(), określasz ilość pamięci do przydzielenia. Rzeczywista ilość używanej pamięci jest nieco większa i zawiera dodatkowe informacje, które rejestrują (przynajmniej), jak duży jest blok. Nie możesz (wiarygodnie) uzyskać dostępu do tych innych informacji - i nie powinieneś :-).

Kiedy dzwonisz free(), po prostu patrzy na dodatkowe informacje, aby dowiedzieć się, jak duży jest blok.

Gary McGill
źródło
44
FYI, na przykład BSD musi malloc_size()niezawodnie uzyskiwać dostęp do rozmiaru bloku z poziomu malloc()wskaźnika ed. Ale nie ma niezawodnego, przenośnego sposobu.
laalto
50
Myślę, że ważne jest, aby powiedzieć, że ten dodatkowy blok informacji znajduje się przed zwróconym wskaźnikiem.
Georg Schölly,
39
@gs Cóż, to zależy od implementacji. Ale tak, tam zwykle jest.
Falaina,
31
Czy potrafisz sobie wyobrazić horror, gdyby free()wymagał od programisty dokładnego zgłoszenia wielkości malloc()bloku? Wycieki pamięci są wystarczająco złe.
MusiGenesis
35
Dlaczego te informacje są dostępne dla malloc()i free(), ale musisz przechowywać rozmiar tablicy? Dlaczego nie pozwolą zrobić czegoś takiego, blockSize(ptr)jeśli i tak przechowują informacje?
corsiKa
144

Większość implementacji funkcji alokacji pamięci C będzie przechowywać informacje rozliczeniowe dla każdego bloku, w linii lub osobno.

Jednym typowym sposobem (w linii) jest faktyczne przydzielenie zarówno nagłówka, jak i pamięci, o którą prosiłeś, uzupełnionych do pewnego minimalnego rozmiaru. Na przykład, jeśli poprosiłeś o 20 bajtów, system może przydzielić blok 48-bajtowy:

  • 16-bajtowy nagłówek zawierający rozmiar, specjalny znacznik, sumę kontrolną, wskaźniki do następnego / poprzedniego bloku i tak dalej.
  • 32-bajtowy obszar danych (20 bajtów uzupełniono do wielokrotności 16).

Adres podany następnie jest adresem obszaru danych. Następnie, kiedy zwolnisz blok, freepo prostu weźmie podany adres i, zakładając, że nie upchnąłeś tego adresu lub pamięci wokół niego, sprawdź informacje księgowe bezpośrednio przed nim. Graficznie byłoby to następująco:

 ____ The allocated block ____
/                             \
+--------+--------------------+
| Header | Your data area ... |
+--------+--------------------+
          ^
          |
          +-- The address you are given

Należy pamiętać, że rozmiar nagłówka i wypełnienia są całkowicie zdefiniowane dla implementacji (właściwie cała rzecz jest zdefiniowana dla implementacji (a), ale powszechna jest opcja rozliczania w linii).

Sumy kontrolne i specjalne znaczniki, które istnieją w informacjach księgowych, są często przyczyną błędów, takich jak „uszkodzona arena pamięci” lub „podwójne zwolnienie”, jeśli je zastąpisz lub zwolnisz dwukrotnie.

Wypełnianie (aby zwiększyć efektywność alokacji) sprawia, że ​​czasami możesz pisać trochę poza końcem żądanego miejsca bez powodowania problemów (nadal nie rób tego, jest to niezdefiniowane zachowanie i tylko dlatego, że czasami działa, nie robi tego oznacza to, że można to zrobić).


(a) Napisałem implementacje mallocsystemów wbudowanych, w których masz 128 bajtów bez względu na to, o co prosiłeś (był to rozmiar największej struktury w systemie), zakładając, że poprosiłeś o 128 bajtów lub mniej (prośby o więcej byłyby zostać spełniony z wartością NULL zwracaną). Bardzo prosta maska ​​bitowa (tj. Nie w linii) została użyta do podjęcia decyzji, czy porcja 128-bajtowa została przydzielona, ​​czy nie.

Inne, które opracowałem, miały różne pule dla fragmentów 16-bajtowych, fragmentów 64-bajtowych, fragmentów 256-bajtowych i 1K, ponownie za pomocą maski bitowej, aby zdecydować, które bloki zostały użyte lub dostępne.

Obie te opcje pozwoliły zmniejszyć obciążenie informacji księgowych oraz zwiększyć szybkość malloci free(nie trzeba łączyć sąsiednich bloków podczas uwalniania), szczególnie ważne w środowisku, w którym pracowaliśmy.

paxdiablo
źródło
@paxdiablo Czy to oznacza, że ​​malloc nie przydziela ciągłych bloków pamięci?
user10678,
2
@ user10678, jedynym prawdziwym wymogiem mallocjest to, że w przypadku udanego przypadku otrzymasz blok pamięci co najmniej tak duży, jak to, o co prosiłeś. Poszczególne bloki są ciągłe pod względem sposobu uzyskiwania dostępu do elementów w nich zawartych, ale nie ma wymogu, aby areny, z których pochodzą bloki, były ciągłe.
paxdiablo
Powiązane pytanie: Dlaczego nie ma odmiany malloc / free, w której określasz rozmiar podczas zwalniania, więc nie musi on przechowywać rozmiaru?
user253751,
@ user253751, bo wtedy theer jeden więcej rzeczy trzeba śledzić, ponad samego wskaźnika. To zarówno niepotrzebna i niebezpieczna void *x = malloc(200); free(x, 500);jest nie skończy się dobrze :-) W każdym razie, dla skuteczności, rzeczywista wielkość bufora może być większy (po prostu nie można powoływać się na ten temat).
paxdiablo,
@paxdiablo Unika także marnowania pamięci na utrzymanie rozmiaru.
user253751,
47

Z comp.lang.clisty najczęściej zadawanych pytań: Skąd darmo wie, ile bajtów do uwolnienia?

Implementacja malloc / free zapamiętuje rozmiar każdego bloku, ponieważ jest on przydzielany, więc nie trzeba przypominać mu o wielkości podczas zwalniania. (Zazwyczaj rozmiar jest zapisywany w sąsiedztwie przydzielonego bloku, dlatego rzeczy zwykle się psują, jeśli granice przydzielonego bloku są nawet nieco przekroczone)

Jdehaan
źródło
2
To nie jest odpowiedź. Pytanie jest dokładnie następujące: dlaczego swobodnie można w wiarygodny sposób sprawdzić rozmiar bloku, ale programista nie ma dostępnej funkcji, która to robi?
Bananach
Jest to rzeczywiście szczegół implementacji dla interfejsu API Malloc i nie ma interfejsu API, aby odzyskać te informacje w standardowy sposób (o ile mi wiadomo). „System” rejestruje to i wykorzystuje na free. Być może odpowiedź nie jest dla ciebie satysfakcjonująca, ale nie sądzę, abyś dostał taką z
ogólniej
6

Ta odpowiedź została przeniesiona z Skąd free () wie, ile pamięci należy zwolnić? gdzie najwidoczniej nie udało mi się odpowiedzieć na pozornie zdublowane pytanie. Ta odpowiedź powinna zatem odnosić się do tego duplikatu:


W przypadku mallocalokatora sterty przechowuje mapowanie oryginalnego zwróconego wskaźnika na odpowiednie szczegóły potrzebne do freepóźniejszego wczytania pamięci. Zazwyczaj obejmuje to przechowywanie wielkości obszaru pamięci w dowolnej formie odpowiedniej dla używanego alokatora, na przykład rozmiaru surowego lub węzła w drzewie binarnym używanym do śledzenia przydziałów lub liczby używanych „jednostek” pamięci.

freenie zawiedzie, jeśli „zmienisz nazwę” wskaźnika lub powielisz go w jakikolwiek sposób. Nie jest to jednak liczone odniesienie i tylko pierwszy freebędzie poprawny. Dodatkowe frees to błędy „podwójnego bezpłatnego”.

Próba freedowolnego wskaźnika o wartości innej niż zwracana przez poprzednie mallocs, a jak dotąd niewyleczona, jest błędem. Nie można częściowo zwolnić regionów pamięci zwróconych malloc.

Matt Joiner
źródło
Zmieniłem wartość wskaźnika zwróconego przez wywołanie malloc. I uwolniłem go bez błędu. Dlaczego? Zobacz tutaj: stackoverflow.com/questions/42618390/…
smwikipedia
4

W powiązanej notatce Biblioteka GLib ma funkcje alokacji pamięci, które nie zapisują niejawnego rozmiaru - a następnie przekazujesz parametr size, aby zwolnić. Może to wyeliminować część kosztów ogólnych.

EFraim
źródło
3

malloc()i free()są zależne od systemu / kompilatora, więc trudno jest podać konkretną odpowiedź.

Więcej informacji na temat tego drugiego pytania .

LiraNuna
źródło
2
Są naprawdę zależne od biblioteki (zazwyczaj biblioteka C, która jest zwykle bardzo ściśle związana z systemem operacyjnym). Dla kompilatora są to tylko funkcje.
Donal Fellows
2

Menedżer sterty przechowywał gdzieś pamięć należącą do przydzielonego bloku, gdy została wywołana malloc.

Sam go nigdy nie wdrożyłem, ale myślę, że pamięć bezpośrednio przed przydzielonym blokiem może zawierać meta informacje.

Timbo
źródło
3
To jedna z możliwych implementacji, ale można by opracować system, w którym cała pamięć jest śledzona w jednej tabeli na zupełnie innej stronie, niekoniecznie w pobliżu puli pamięci, z której jest przydzielana.
ephemient
2

Oryginalną techniką było przydzielenie nieco większego bloku i zapisanie rozmiaru na początku, a następnie przekazanie aplikacji pozostałej części bloga. Dodatkowa przestrzeń zawiera rozmiar i ewentualnie linki do połączenia wolnych bloków w celu ponownego użycia.

Istnieją jednak pewne problemy z tymi sztuczkami, takie jak słaba pamięć podręczna i zachowanie zarządzania pamięcią. Używanie pamięci bezpośrednio w bloku ma tendencję do niepotrzebnego tworzenia stron, a także tworzy brudne strony, które komplikują udostępnianie i kopiowanie przy zapisie.

Tak więc bardziej zaawansowaną techniką jest prowadzenie osobnego katalogu. Opracowano także podejścia egzotyczne, w których obszary pamięci wykorzystują tę samą moc dwóch rozmiarów.

Ogólnie rzecz biorąc, odpowiedź brzmi: oddzielna struktura danych jest przypisana do zachowania stanu.

DigitalRoss
źródło
1

Aby odpowiedzieć na drugą połowę pytania: tak, możesz, a dość powszechny wzór w C jest następujący:

typedef struct {
    size_t numElements
    int elements[1]; /* but enough space malloced for numElements at runtime */
} IntArray_t;

#define SIZE 10
IntArray_t* myArray = malloc(sizeof(intArray_t) + SIZE * sizeof(int));
myArray->numElements = SIZE;
MSalters
źródło
Jest to zupełnie inna technika niż ta stosowana przez malloc BSD dla małych obiektów (choć jest to doskonale dobra technika tworzenia tablic w stylu Pascala)
Pete Kirkham,
0

Kiedy nazywamy malloc, po prostu zużywa więcej bajtów od jego wymagań. To bardziej bajtowe zużycie zawiera informacje, takie jak suma kontrolna, rozmiar i inne dodatkowe informacje. Kiedy dzwonimy za darmo w tym czasie, bezpośrednio przechodzimy do tych dodatkowych informacji, w których znajduje się adres, a także znajduje się informacja o tym, ile bloków będzie wolne.

Varun Chhangani
źródło
0

aby odpowiedzieć na drugie pytanie, tak, możesz (w pewnym sensie) skorzystać z tej samej techniki, jak malloc() po prostu przypisując pierwszą komórkę w każdej tablicy do rozmiaru tablicy. która pozwala wysłać tablicę bez wysyłania argumentu o dodatkowym rozmiarze.

avish
źródło