Żeby było jasne: wiem to malloc
i free
są zaimplementowane w bibliotece C, która zwykle alokuje fragmenty pamięci z systemu operacyjnego i samodzielnie zarządza, aby rozdzielić mniejsze partie pamięci do aplikacji i śledzi liczbę przydzielonych bajtów . To pytanie nie brzmi: Skąd wolny wie, ile za darmo .
Raczej chciałbym wiedzieć, dlaczego free
powstał w ten sposób w pierwszej kolejności. Będąc językiem niskiego poziomu, myślę, że byłoby całkiem rozsądne poprosić programistę C o śledzenie nie tylko tego, jaka pamięć została przydzielona, ale także ile (w rzeczywistości często uważam, że w końcu śledzę liczbę bajtów w każdym razie Malloced). Wydaje mi się również, że jawne podanie liczby bajtów free
może pozwolić na pewne optymalizacje wydajności, np. Alokator, który ma oddzielne pule dla różnych rozmiarów alokacji, byłby w stanie określić, z której puli zwolnić, po prostu patrząc na argumenty wejściowe, i ogólnie byłoby mniej miejsca.
Krótko mówiąc, dlaczego zostały malloc
i zostały free
stworzone w taki sposób, że wymagają wewnętrznego śledzenia liczby przydzielonych bajtów? Czy to tylko historyczny wypadek?
Mała zmiana: kilka osób podało punkty, takie jak „a co, jeśli uwolnisz inną kwotę niż ta, którą przydzieliłeś”. Moje wyobrażone API mogłoby po prostu wymagać zwolnienia dokładnie określonej liczby przydzielonych bajtów; uwolnienie więcej lub mniej może być po prostu zdefiniowane jako UB lub implementacja. Nie chcę jednak zniechęcać do dyskusji na temat innych możliwości.
źródło
malloc
nie zna rozmiaru zwracanego bloku .malloc
często zwraca blok, który jest większy niż żądany. W najlepszym przypadku programista mógłby przekazać rozmiar żądany wmalloc()
wywołaniu, co wcale nie pomogłoby wdrażającemufree()
.Odpowiedzi:
Jeden argument
free(void *)
(wprowadzony w Unix V7) ma jeszcze jedną dużą przewagę nad wcześniejszymi dwoma argumentami, omfree(void *, size_t)
których tu nie wspomniałem: jeden argumentfree
radykalnie upraszcza każde inne API, które działa z pamięcią sterty. Na przykład, w raziefree
potrzeby, rozmiar bloku pamięci,strdup
musiałby w jakiś sposób zwrócić dwie wartości (wskaźnik + rozmiar) zamiast jednej (wskaźnik), a C sprawia, że zwroty wielu wartości są znacznie bardziej uciążliwe niż zwroty jednowartościowe. Zamiast tegochar *strdup(char *)
musielibyśmy pisaćchar *strdup(char *, size_t *)
albo inaczejstruct CharPWithSize { char *val; size_t size}; CharPWithSize strdup(char *)
. (Obecnie ta druga opcja wygląda dość kusząco, ponieważ wiemy, że ciągi zakończone znakiem NUL są "najbardziej katastrofalnym błędem projektowym w historii komputerów", ale to z perspektywy czasu. Jeszcze w latach 70-tych zdolność C do obsługi łańcuchów jako prostegochar *
traktowania była faktycznie uważana za decydującą przewagę nad konkurentami, takimi jak Pascal i Algol .) Poza tym nie tylkostrdup
cierpi na ten problem - wpływa na każdy system lub zdefiniowany przez użytkownika funkcja, która przydziela pamięć sterty.Wcześni projektanci Uniksa byli bardzo sprytnymi ludźmi i jest wiele powodów, dla których
free
jest lepszy niżmfree
taki, w zasadzie myślę, że odpowiedź na pytanie jest taka, że zauważyli to i odpowiednio zaprojektowali swój system. Wątpię, czy znajdziesz jakikolwiek bezpośredni zapis tego, co działo się w ich głowach w momencie, gdy podjęli tę decyzję. Ale możemy sobie wyobrazić.Udawaj, że piszesz aplikacje w C, aby działały na V6 Unix, z jego dwoma argumentami
mfree
. Jak dotąd radziłeś sobie dobrze, ale śledzenie tych rozmiarów wskaźników staje się coraz bardziej kłopotliwe, ponieważ twoje programy stają się coraz bardziej ambitne i wymagają coraz częstszego używania zmiennych alokowanych na stercie. Ale masz genialny pomysł: zamiast ciągłego ich kopiowaniasize_t
, możesz po prostu napisać kilka funkcji narzędziowych, które przechowują rozmiar bezpośrednio w przydzielonej pamięci:void *my_alloc(size_t size) { void *block = malloc(sizeof(size) + size); *(size_t *)block = size; return (void *) ((size_t *)block + 1); } void my_free(void *block) { block = (size_t *)block - 1; mfree(block, *(size_t *)block); }
Im więcej kodu napiszesz przy użyciu tych nowych funkcji, tym bardziej będą one niesamowite. Nie tylko uczynić kod łatwiej napisać, ale również uczynić swój kod szybciej - dwie rzeczy, które często nie idą w parze! Wcześniej przekazywałeś je
size_t
wszędzie, co powodowało dodatkowe obciążenie procesora związane z kopiowaniem i oznaczało, że trzeba było częściej rozlewać rejestry (szczególnie w przypadku argumentów funkcji dodatkowych) i marnować pamięć (ponieważ wywołania funkcji zagnieżdżonych często powodują w wielu kopiachsize_t
przechowywanych w różnych ramkach stosu). W nowym systemie nadal musisz wydać pamięć na przechowywanie plikówsize_t
, ale tylko raz i nigdzie nie jest kopiowany. Może się to wydawać niewielkimi korzyściami, ale pamiętaj, że mówimy o maszynach z wyższej półki z 256 KB pamięci RAM.To cię uszczęśliwia! Więc dzielisz się swoją fajną sztuczką z brodatymi mężczyznami, którzy pracują nad następną wersją Uniksa, ale to ich nie uszczęśliwia, tylko zasmuca. Widzisz, byli właśnie w trakcie dodawania kilku nowych funkcji narzędziowych, takich jak
strdup
, i zdają sobie sprawę, że ludzie używający twojej fajnej sztuczki nie będą w stanie używać swoich nowych funkcji, ponieważ wszystkie ich nowe funkcje używają kłopotliwego wskaźnika + rozmiar API. I to też sprawia, że jesteś smutny, ponieważ zdajesz sobie sprawę, że będziesz musiał sam przepisać dobrąstrdup(char *)
funkcję w każdym programie, który napiszesz, zamiast korzystać z wersji systemu.Ale poczekaj! Jest rok 1977, a kompatybilność wsteczna nie zostanie wynaleziona przez kolejne 5 lat! A poza tym nikt poważny nie używa tej niejasnej „uniksowej” rzeczy z jej niekolorową nazwą. Pierwsza edycja K&R jest już w drodze do wydawcy, ale to żaden problem - na pierwszej stronie jest napisane, że „C nie udostępnia żadnych operacji bezpośrednio zajmujących się obiektami złożonymi, takimi jak ciągi znaków… nie ma sterty … ”. W tym momencie w historii
string.h
imalloc
są to rozszerzenia dostawców (!). Tak więc, sugeruje Bearded Man # 1, możemy je zmieniać, jak chcemy; dlaczego po prostu nie zadeklarujemy, że Twój trudny dzielnik jest oficjalnym dystrybutorem?Kilka dni później Bearded Man # 2 widzi nowe API i mówi hej, czekaj, to jest lepsze niż wcześniej, ale nadal spędza całe słowo na alokację, przechowując rozmiar. Uważa to za kolejną rzecz do bluźnierstwa. Wszyscy inni patrzą na niego, jakby był szalony, bo co innego możesz zrobić? Tej nocy zostaje do późna i wymyśla nowy alokator, który w ogóle nie przechowuje rozmiaru, ale zamiast tego wnioskuje o nim w locie, wykonując czarną magię przesunięcia bitów na wartości wskaźnika i zamienia go, utrzymując nowe API na miejscu. Nowe API oznacza, że nikt nie zauważa przełączenia, ale zauważa, że następnego ranka kompilator zużywa o 10% mniej pamięci RAM.
A teraz wszyscy są szczęśliwi: otrzymujesz łatwiejszy do napisania i szybszy kod, Brodaty # 1 może napisać fajny prosty,
strdup
którego ludzie będą naprawdę używać, a Brodaty # 2 - pewny, że zasłużył na trochę na utrzymanie - - wraca do zabawy z quinami . Wyślij to!A przynajmniej tak mogło się stać.
źródło
Ponieważ nie ma takiej potrzeby, a i tak nie miałoby to sensu .
Kiedy coś przydzielasz, chcesz powiedzieć systemowi, ile bajtów ma przydzielić (z oczywistych powodów).
Jednak po przydzieleniu obiektu rozmiar odzyskiwanego regionu pamięci jest teraz określany. To ukryte. To jeden ciągły blok pamięci. Nie możesz cofnąć przydziału części (zapomnijmy
realloc()
, że i tak nie to robi), możesz tylko cofnąć przydział całej rzeczy. Nie możesz też "zwolnić X bajtów" - albo zwalniasz blok pamięci, który dostałeś,malloc()
albo nie.A teraz, jeśli chcesz go zwolnić, możesz po prostu powiedzieć systemowi zarządzania pamięcią: „oto ten wskaźnik,
free()
blok, na który wskazuje”. - a menedżer pamięci będzie wiedział, jak to zrobić, albo dlatego, że domyślnie zna rozmiar, albo może nawet nie potrzebować rozmiaru.Na przykład, większość typowych implementacji
malloc()
utrzymuje połączoną listę wskaźników do wolnych i przydzielonych bloków pamięci. Jeśli przekażesz wskaźnik dofree()
, po prostu wyszuka ten wskaźnik na liście „przydzielonych”, odłączy odpowiedni węzeł i dołączy go do listy „wolnej”. Nie potrzebował nawet rozmiaru regionu. Będzie potrzebować tych informacji tylko wtedy, gdy potencjalnie spróbuje ponownie wykorzystać dany blok.źródło
C może nie jest tak „abstrakcyjny” jak C ++, ale nadal ma być abstrakcją w stosunku do asemblacji. W tym celu z równania usuwa się szczegóły najniższego poziomu. Zapobiega to w większości przypadków wyrównywania i wypełniania, co spowodowałoby, że wszystkie programy w C byłyby nieprzenośne.
Krótko mówiąc, na tym polega cały sens pisania abstrakcji .
źródło
malloc
o N bajtów i zamiast tego zwrócił wskaźnik na początek całej strony (z powodu wyrównania, dopełnienia lub innych ograniczeń , użytkownik nie mógłby tego śledzić - zmuszając go do zrobienie tego przyniosłobymalloc
może po prostu zawsze zwrócić wyrównany wskaźnik bez zapisywania rozmiaru alokacji.free
może następnie zaokrąglić w górę do odpowiedniego wyrównania, aby upewnić się, że wszystko zostało prawidłowo uwolnione. Nie wiem, gdzie jest problem.size
parametrufree
otwierającego kolejne źródło błędów.W rzeczywistości, w starożytnym alokatorze pamięci jądra Unix,
mfree()
wziąłsize
argument.malloc()
imfree()
zachował dwie tablice (jedną dla pamięci rdzenia, drugą dla wymiany), które zawierały informacje o wolnych adresach i rozmiarach bloków.Nie było alokatora przestrzeni użytkownika aż do Unix V6 (programy po prostu używały
sbrk()
). W Unix V6, iolib zawierał z przydzielaniaalloc(size)
ifree()
połączenia, które nie brały argument rozmiar. Każdy blok pamięci poprzedzony był swoim rozmiarem i wskaźnikiem do następnego bloku. Wskaźnik był używany tylko w przypadku wolnych bloków podczas przechodzenia po liście wolnych i był ponownie używany jako pamięć bloków w blokach będących w użyciu.W Unix 32V i Unix V7 zostało to zastąpione przez nową
malloc()
ifree()
implementację, w którejfree()
nie wziąłemsize
argumentu. Implementacja była listą cykliczną, każda porcja była poprzedzona słowem, które zawierało wskaźnik do następnej porcji oraz bit „zajętości” (przydzielony). Więcmalloc()/free()
nawet nie śledził wyraźnego rozmiaru.źródło
Ponieważ nie musi. Informacje są już dostępne w zarządzaniu wewnętrznym prowadzonym przez malloc / free.
Oto dwie kwestie (które mogły przyczynić się do tej decyzji lub nie):
Dlaczego miałbyś oczekiwać, że funkcja otrzyma parametr, którego nie potrzebuje?
(skomplikowałoby to praktycznie cały kod klienta zależny od pamięci dynamicznej i spowodowałoby zupełnie niepotrzebną redundancję w aplikacji). Śledzenie alokacji wskaźników jest już trudnym problemem. Śledzenie alokacji pamięci wraz z powiązanymi rozmiarami niepotrzebnie zwiększyłoby złożoność kodu klienta.
Co
free
w takich przypadkach zrobiłaby zmieniona funkcja?void * p = malloc(20); free(p, 25); // (1) wrong size provided by client code free(NULL, 10); // (2) generic argument mismatch
Czy nie byłoby wolne (spowodowałoby wyciek pamięci?)? Zignorować drugi parametr? Zatrzymać aplikację, wywołując exit? Wdrożenie tego dodałoby dodatkowe punkty awarii w twojej aplikacji, dla funkcji, której prawdopodobnie nie potrzebujesz (a jeśli jej potrzebujesz, zobacz mój ostatni punkt poniżej - „wdrażanie rozwiązania na poziomie aplikacji”).
Ponieważ jest to „właściwy” sposób, aby to zrobić. API powinno wymagać argumentów potrzebnych do wykonania swojej operacji, i nie więcej .
Właściwe sposoby na wdrożenie tego to:
(na poziomie systemu) w ramach implementacji malloc - nic nie stoi na przeszkodzie, aby osoba wdrażająca bibliotekę napisała malloc, aby wewnętrznie stosował różne strategie w oparciu o otrzymany rozmiar.
(na poziomie aplikacji), pakując malloc i free w ramach własnych interfejsów API i używając ich zamiast tego (wszędzie w aplikacji, których możesz potrzebować).
źródło
Przychodzi mi na myśl pięć powodów:
To wygodne. Usuwa całe obciążenie programisty i unika klasy niezwykle trudnych do śledzenia błędów.
Otwiera możliwość zwolnienia części bloku. Ale ponieważ menedżerowie pamięci zwykle chcą mieć informacje o śledzeniu, nie jest jasne, co to by oznaczało?
Lightness Races In Orbit to punkt odniesienia w kwestii wypełnienia i wyrównania. Charakter zarządzania pamięcią oznacza, że rzeczywisty przydzielony rozmiar prawdopodobnie różni się od rozmiaru, o który prosiłeś. Oznacza to, że
free
w przypadku konieczności zmiany rozmiaru i lokalizacjimalloc
należałoby zmienić również faktyczny przydzielony rozmiar.I tak nie jest jasne, czy przekazanie rozmiaru przynosi jakiekolwiek rzeczywiste korzyści. Typowy menedżer pamięci ma 4-16 bajtów nagłówka na każdy fragment pamięci, w tym rozmiar. Ten nagłówek fragmentu może być wspólny dla przydzielonej i nieprzydzielonej pamięci, a gdy sąsiednie fragmenty zostaną zwolnione, mogą zostać zwinięte razem. Jeśli sprawisz, że wywołujący przechowuje wolną pamięć, możesz zwolnić prawdopodobnie 4 bajty na porcję, nie mając osobnego pola rozmiaru w przydzielonej pamięci, ale to pole rozmiaru prawdopodobnie i tak nie zostanie uzyskane, ponieważ dzwoniący musi gdzieś je przechowywać. Ale teraz ta informacja jest rozproszona w pamięci, a nie jest przewidywalnie zlokalizowana w porcji nagłówka, która i tak prawdopodobnie będzie mniej wydajna operacyjnie.
Nawet jeśli to było bardziej efektywne to radykalnie nieprawdopodobne program spędza dużą ilość czasu uwalniając pamięć i tak więc korzyści byłyby bardzo małe.
Nawiasem mówiąc, Twój pomysł dotyczący oddzielnych alokatorów dla elementów o różnej wielkości można łatwo wdrożyć bez tych informacji (możesz użyć adresu, aby określić, gdzie nastąpiła alokacja). Robi się to rutynowo w C ++.
Dodano później
Inna odpowiedź, dość śmieszna, wywołała std :: podzielnik jako dowód, który
free
może działać w ten sposób, ale w rzeczywistości służy jako dobry przykład tego, dlaczegofree
nie działa w ten sposób. Istnieją dwie kluczowe różnice między tym, comalloc
/free
do, a tym, co robi std :: alokator. Po pierwsze,malloc
ifree
są skierowane użytkownika - są one przeznaczone do ogólnych programistów do pracy z - natomiaststd::allocator
ma na celu dostarczenie specjalistycznej alokacji pamięci do biblioteki standardowej. To dobry przykład, kiedy pierwszy z moich punktów nie ma lub nie ma znaczenia. Ponieważ jest to biblioteka, trudności z obsługą złożoności rozmiaru śledzenia są i tak ukryte przed użytkownikiem.Po drugie, std :: alokator zawsze działa z pozycją o tym samym rozmiarze, co oznacza, że możliwe jest użycie pierwotnie przekazanej liczby elementów do określenia, ile jest wolnego. To, dlaczego to różni się od
free
siebie, jest ilustracyjne. Wstd::allocator
pozycjach, które mają być przydzielone, są zawsze tego samego, znanego rozmiaru i zawsze tego samego rodzaju, więc zawsze mają te same wymagania dotyczące dostosowania. Oznacza to, że alokator może być wyspecjalizowany w prostym przydzielaniu tablicy tych elementów na początku i rozdzielaniu ich w razie potrzeby. Nie można tego zrobić,free
ponieważ nie ma sposobu, aby zagwarantować, że najlepszy rozmiar do zwrócenia to rozmiar, o który prosisz, zamiast tego znacznie bardziej wydajne jest czasami zwrócenie większych bloków niż prosi dzwoniący *, a zatem alboużytkownik lub menedżer musi śledzić dokładny faktycznie przyznany rozmiar. Przekazywanie tego rodzaju szczegółów implementacji użytkownikowi jest niepotrzebnym bólem głowy, który nie daje dzwoniącemu żadnych korzyści.- * Jeśli ktoś nadal ma trudności ze zrozumieniem tego punktu, rozważ to: typowy alokator pamięci dodaje niewielką ilość informacji śledzenia na początek bloku pamięci, a następnie zwraca przesunięcie wskaźnika z tego. Przechowywane tutaj informacje zazwyczaj zawierają na przykład wskaźniki do następnego wolnego bloku. Załóżmy, że nagłówek ma zaledwie 4 bajty długości (co jest w rzeczywistości mniejszy niż większość prawdziwych bibliotek) i nie zawiera rozmiaru, a następnie wyobraźmy sobie, że mamy 20-bajtowy blok, gdy użytkownik prosi o 16-bajtowy blok, naiwny system zwróciłby 16-bajtowy blok, ale zostawiłby 4-bajtowy fragment, który nigdy, przenigdy nie mógłby zostać użyty, tracąc czas za każdym razem
malloc
zostanie wezwany. Jeśli zamiast tego menedżer po prostu zwróci 20-bajtowy blok, wówczas oszczędza te nieporządne fragmenty przed tworzeniem się i jest w stanie dokładniej alokować dostępną pamięć. Ale jeśli system ma to zrobić poprawnie bez śledzenia samego rozmiaru, wymagamy od użytkownika śledzenia - dla każdej, pojedynczej alokacji - ilości faktycznie przydzielonej pamięci, jeśli ma ją przekazać z powrotem za darmo. Ten sam argument dotyczy dopełnienia dla typów / alokacji, które nie pasują do pożądanych granic. Zatem co najwyżej wymaganie rozmiaru, który byłby łatwy do obsłużenia przez każdego rozsądnego menedżera pamięci.free
przyjęcia rozmiaru jest albo (a) całkowicie bezużyteczne, ponieważ alokator pamięci nie może polegać na przekazanym rozmiarze, aby dopasować się do faktycznie przydzielonego rozmiaru, lub (b) bezcelowo wymaga, aby użytkownik wykonywał pracę śledzącą rzeczywistyźródło
free
wywołanie ma poprawnie przekazać rozmiar do zwolnienia, musi to wiedzieć.Publikuję to tylko jako odpowiedź nie dlatego, że jest to ta, na którą masz nadzieję, ale dlatego, że uważam, że jest to jedyna wiarygodnie poprawna:
Prawdopodobnie pierwotnie uznano to za wygodne i później nie można go było ulepszyć.
Prawdopodobnie nie ma ku temu przekonującego powodu. (Ale szczęśliwie usunę to, jeśli okaże się, że jest niepoprawne.)
Nie będzie mieć korzyści, jeśli to było możliwe: można przeznaczyć jeden duży kawałek pamięci o rozmiarze wiedział wcześniej, to uwolnić się trochę w czasie - w przeciwieństwie do wielokrotnego przydzielania i zwalniania małych fragmentów pamięci. Obecnie takie zadania nie są możliwe.
Dla wielu (wielu 1 !) Z was, którzy uważają, że przekroczenie rozmiaru jest tak niedorzeczne:
Czy mogę odesłać Cię do decyzji projektowej C ++ dotyczącej
std::allocator<T>::deallocate
metody?void deallocate(pointer p, size_type n);
Myślę, że będziesz miał raczej „interesujący” czas na analizę tej decyzji projektowej.
Jeśli chodzi o
operator delete
, okazuje się, że propozycja 2013 N3778 („ Deallocation Sized Sized C ++”) również ma to naprawić.1 Wystarczy spojrzeć na komentarze pod pierwotnym pytaniem, aby zobaczyć, ile osób poczyniło pochopne stwierdzenia, takie jak „zapytany o rozmiar jest całkowicie bezużyteczny dla
free
wywołania”, aby uzasadnić braksize
parametru.źródło
malloc()
wdrożenia wyeliminowałoby to również konieczność pamiętania czegokolwiek o przydzielonym regionie, zmniejszając narzut alokacji do narzutu związanego z dopasowaniem.malloc()
byłby w stanie samodzielnie wykonać całą księgowość w ramach uwolnionych fragmentów. Istnieją przypadki użycia, w których byłaby to duża poprawa. Należy jednak odradzać przydzielanie dużej porcji pamięci na kilka małych porcji, jednak spowodowałoby to drastyczne zwiększenie fragmentacji.std::allocator
przydziela tylko elementy o określonej, znanej wielkości. Nie jest to podzielnik ogólnego przeznaczenia, w porównaniu jabłka do pomarańczy.malloc i free idą w parze, a każdemu „malloc” odpowiada jeden „wolny”. Dlatego też ma całkowity sens, że „bezpłatne” dopasowanie do poprzedniego „malloc” powinno po prostu zwolnić ilość pamięci przydzielonej przez to malloc - jest to większość przypadków użycia, które miałyby sens w 99% przypadków. Wyobraź sobie wszystkie błędy pamięci, gdyby wszystkie zastosowania malloc / free przez wszystkich programistów na całym świecie wymagałyby od programisty śledzenia kwoty przydzielonej w malloc, a następnie pamiętania o zwolnieniu tego samego. Scenariusz, o którym mówisz, powinien naprawdę wykorzystywać wiele funkcji Mallocs / Frees w jakiejś implementacji zarządzania pamięcią.
źródło
gets
,printf
pętle ręczne (off-by-one), niezdefiniowanych zachowań Format-strings, konwersje niejawne, bit sztuczki, ET cetera.Sugerowałbym, że dzieje się tak, ponieważ jest bardzo wygodne, aby nie musieć ręcznie śledzić informacji o rozmiarze w ten sposób (w niektórych przypadkach), a także jest mniej podatne na błędy programisty.
Ponadto realloc potrzebowałby tych informacji księgowych, które, jak sądzę, zawierają więcej niż tylko rozmiar alokacji. tzn. pozwala na zdefiniowanie mechanizmu, za pomocą którego działa.
Możesz jednak napisać swój własny alokator, który działał w sposób, który sugerujesz, i często jest to robione w języku c ++ dla alokatorów puli w podobny sposób dla określonych przypadków (z potencjalnie ogromnym wzrostem wydajności), chociaż jest to generalnie realizowane w kategoriach operatora nowość w przydzielaniu bloków puli.
źródło
Nie rozumiem, jak działałby alokator, który nie śledzi rozmiaru swoich przydziałów. Gdyby tego nie zrobił, skąd wiedziałby, która pamięć jest dostępna, aby zaspokoić przyszłe
malloc
żądanie? Musi przynajmniej przechowywać jakąś strukturę danych zawierającą adresy i długości, aby wskazać, gdzie są dostępne bloki pamięci. (Oczywiście przechowywanie listy wolnych miejsc jest równoznaczne z przechowywaniem listy przydzielonych miejsc).źródło
Cóż, jedyne, czego potrzebujesz, to wskaźnik, którego użyjesz do zwolnienia wcześniej przydzielonej pamięci. Ilość bajtów jest zarządzana przez system operacyjny, więc nie musisz się tym martwić. Nie byłoby konieczne pobieranie liczby przydzielonych bajtów zwracanych przez funkcję free (). Proponuję ręczny sposób zliczania liczby bajtów / pozycji przydzielonych przez uruchomiony program:
Jeśli pracujesz w Linuksie i chcesz poznać ilość bajtów / pozycji przydzielonych przez malloc, możesz stworzyć prosty program, który używa malloc raz lub n razy i wypisuje otrzymane wskaźniki. Ponadto musisz uśpić program na kilka sekund (wystarczy, abyś mógł wykonać poniższe czynności). Następnie uruchom ten program, poszukaj jego PID, napisz cd / proc / process_PID i po prostu wpisz „cat maps”. Dane wyjściowe pokażą, w jednej określonej linii, zarówno początkowy, jak i końcowy adres pamięci obszaru pamięci sterty (tego, w którym alokujesz pamięć dynamicznie). Jeśli wydrukujesz wskaźniki do tych obszarów pamięci, które są przydzielane, można odgadnąć, ile pamięci zostało przydzielone.
Mam nadzieję, że to pomoże!
źródło
Dlaczego miałoby to robić? malloc () i free () są celowo bardzo prostymi prymitywami zarządzania pamięcią , a zarządzanie pamięcią wyższego poziomu w języku C w dużej mierze zależy od programisty. T
Co więcej, realloc () już to robi - jeśli zmniejszysz alokację w realloc (), czy nie spowoduje to przeniesienia danych, a zwrócony wskaźnik będzie taki sam jak oryginał.
Ogólnie rzecz biorąc, cała biblioteka standardowa składa się z prostych prymitywów, z których można budować bardziej złożone funkcje, aby dopasować je do potrzeb aplikacji. Zatem odpowiedź na każde pytanie w postaci „dlaczego standardowa biblioteka nie obsługuje X” jest taka, że nie może zrobić wszystkiego, o czym programiści mogą pomyśleć (po to są programiści), więc decyduje się zrobić bardzo mało - zbudować własną lub używać bibliotek innych firm. Jeśli potrzebujesz bardziej rozbudowanej biblioteki standardowej - w tym bardziej elastycznego zarządzania pamięcią, C ++ może być odpowiedzią.
Oznaczyłeś pytanie C ++, a także C, a jeśli C ++ jest tym, czego używasz, to w żadnym wypadku nie powinieneś używać malloc / free - poza new / delete, klasy kontenerów STL zarządzają pamięcią automatycznie i w sposób prawdopodobny być szczególnie odpowiednie do charakteru różnych pojemników.
źródło
realloc
jest absolutnie dozwolone do przenoszenia danych. Zgodnie ze standardem C implementacjarealloc
jakomalloc
+memcpy
+ jest całkowicie legalnafree
. I są dobre powody, dla których implementacja może chcieć przenieść alokację, która została zmniejszona, np. Aby uniknąć fragmentacji pamięci.