Co NAPRAWDĘ dzieje się, gdy nie zwalniasz po Malloc?

538

To mnie martwiło od wieków.

Wszyscy uczymy się w szkole (przynajmniej ja byłem), że MUSISZ uwolnić każdy przydzielony wskaźnik. Jestem jednak trochę ciekawy, jakie są rzeczywiste koszty nie zwalniania pamięci. W niektórych oczywistych przypadkach, takich jak mallocwywołanie w pętli lub części wykonania wątku, bardzo ważne jest zwolnienie, aby nie było wycieków pamięci. Ale rozważ następujące dwa przykłady:

Po pierwsze, jeśli mam kod, to coś takiego:

int main()
{
    char *a = malloc(1024);
    /* Do some arbitrary stuff with 'a' (no alloc functions) */
    return 0;
}

Jaki jest tutaj prawdziwy wynik? freeUważam, że proces umiera, a następnie przestrzeń stosu i tak nie ma miejsca, więc nie ma żadnych szkód w nieodebraniu wezwania do (jednak zdaję sobie sprawę z tego, jak ważne jest, aby mieć to do zamknięcia, konserwacji i dobrej praktyki). Czy mam rację w tym myśleniu?

Po drugie, powiedzmy, że mam program, który działa trochę jak powłoka. Użytkownicy mogą deklarować zmienne podobne, aaa = 123a te są przechowywane w jakiejś dynamicznej strukturze danych do późniejszego wykorzystania. Najwyraźniej wydaje się oczywiste, że użyłbyś jakiegoś rozwiązania, które wywoła jakąś * funkcję przydzielania (skrót, lista połączona, coś podobnego). W przypadku tego rodzaju programu nie ma sensu nigdy zwalniać po wywołaniu, mallocponieważ zmienne te muszą być obecne przez cały czas podczas wykonywania programu i nie ma dobrego sposobu (jak widzę) na wdrożenie tego przy użyciu statycznie przydzielonego miejsca. Czy źle jest mieć część pamięci, która jest przydzielana, ale zwalniana tylko jako część procesu kończącego się? Jeśli tak, jaka jest alternatywa?

Scott
źródło
7
@NTDLS Magia systemu oceniania faktycznie działa raz: 6 lat i odpowiedź „bardziej zasłużona” rzeczywiście wzrosła na szczyt.
zxq9
15
Ludzie poniżej mówią, że dobry nowoczesny system operacyjny wykonuje czyszczenie, ale co, jeśli kod działa w trybie jądra (np. Ze względu na wydajność)? Czy programy trybu jądra (na przykład w systemie Linux) są w trybie piaskownicy? Jeśli nie, to uważam, że musisz ręcznie uwolnić wszystko, jak sądzę, nawet przed nienormalnymi zakończeniami, takimi jak abort ().
Dr Person Person II
3
@ Dr.PersonPersonII Tak, kod działający w trybie jądra zazwyczaj musi ręcznie wszystko zwalniać.
zwolnienie
1
Chciałbym dodać, że free(a)tak naprawdę nic nie robi, aby zwolnić pamięć! Po prostu resetuje niektóre wskaźniki w implementacji liboc malloc, które śledzą dostępne fragmenty pamięci na dużej stronie pamięci zmapowanej (zwykle nazywanej „stertą”). Ta strona będzie nadal zwalniana dopiero po zakończeniu programu, a nie wcześniej.
Marco Bonelli,
1
Free () może, ale nie musi, faktycznie zwolnić pamięć. Może po prostu oznaczyć blok jako wolny, do odzyskania później lub połączyć go z wolną listą. Może to scalić w sąsiednie wolne bloki lub pozostawić to do wykonania przydziału. To wszystko szczegół implementacji.
Jordan Brown,

Odpowiedzi:

378

Prawie każdy nowoczesny system operacyjny odzyska całą przydzieloną pamięć po zakończeniu programu. Jedynym wyjątkiem, jaki mogę wymyślić, może być coś takiego jak Palm OS, w którym pamięć statyczna i pamięć wykonawcza programu są prawie takie same, więc brak zwolnienia może spowodować, że program zajmie więcej pamięci. (Spekuluję tylko tutaj.)

Ogólnie rzecz biorąc, nie ma w tym żadnej szkody, z wyjątkiem kosztów środowiska wykonawczego posiadania większej ilości miejsca niż potrzebujesz. Z pewnością w podanym przykładzie chcesz zachować pamięć dla zmiennej, która może być używana, dopóki nie zostanie wyczyszczona.

Jednak uważa się za dobry styl, aby zwolnić pamięć, gdy tylko jej nie potrzebujesz, i uwolnić wszystko, co masz jeszcze przy wyjściu z programu. Jest to raczej ćwiczenie polegające na poznaniu używanej pamięci i zastanowieniu się, czy nadal jej potrzebujesz. Jeśli nie będziesz śledzić, być może wycieki pamięci.

Z drugiej strony, podobne upomnienie o zamykaniu plików przy wyjściu ma znacznie bardziej konkretny wynik - jeśli nie, dane, które do nich napisałeś, mogą nie zostać usunięte, lub jeśli są plikami tymczasowymi, mogą nie zostaną usunięte, gdy skończysz. Ponadto uchwyty baz danych powinny zostać zatwierdzone, a następnie zamknięte, gdy skończysz z nimi. Podobnie, jeśli używasz języka zorientowanego obiektowo, takiego jak C ++ lub Objective C, nie zwolnienie obiektu po jego zakończeniu będzie oznaczać, że destruktor nigdy nie zostanie wywołany, a wszelkie zasoby, za które odpowiedzialna jest klasa, mogą nie zostać wyczyszczone.

Paul Tomblin
źródło
16
Prawdopodobnie dobrze byłoby również wspomnieć, że nie wszyscy używają nowoczesnego systemu operacyjnego, jeśli ktoś bierze twój program (i nadal działa na systemie operacyjnym, który nie odzyskuje pamięci), uruchamia go wtedy GG.
user105033,
79
Naprawdę uważam tę odpowiedź za niewłaściwą. Zawsze należy cofnąć przydział zasobów po tym, jak się z nimi skończy, niezależnie od tego, czy są to uchwyty plików / pamięć / muteksy. Mając taki nawyk, nie popełnisz tego rodzaju błędu podczas budowania serwerów. Niektóre serwery powinny działać 24x7. W takich przypadkach jakikolwiek wyciek oznacza, że ​​twój serwer ostatecznie zabraknie tego zasobu i zawiesi się / zawiesi w jakiś sposób. Krótki program narzędziowy, przeciek nie jest taki zły. Każdy serwer, każdy wyciek to śmierć. Zrób sobie przysługę. Posprzątaj po sobie. To dobry nawyk.
EvilTeach
120
Która część „Jednak uważa się za dobry styl, aby zwolnić pamięć, gdy tylko jej nie potrzebujesz, i uwolnić wszystko, co masz jeszcze przy wyjściu z programu”. czy zatem uważasz, że jest źle?
Paul Tomblin
24
Jeśli masz sklep z pamięcią, którego potrzebujesz aż do momentu zamknięcia programu, a nie używasz prymitywnego systemu operacyjnego, zwolnienie pamięci tuż przed wyjściem jest wyborem stylistycznym, a nie defektem.
Paul Tomblin
30
@Paul - Po prostu zgadzanie się z EvilTeach, nie jest uważane za dobry styl do zwalniania pamięci, niepoprawne jest, aby nie zwalniać pamięci. Twoje sformułowanie sprawia, że ​​wydaje się to równie ważne, jak noszenie chustki pasującej do krawata. Właściwie to na poziomie noszenia spodni.
Heath Hunnicutt
110

Tak, masz rację, twój przykład nie wyrządza żadnej szkody (przynajmniej nie w większości nowoczesnych systemów operacyjnych). Cała pamięć przydzielona przez proces zostanie odzyskana przez system operacyjny po zakończeniu procesu.

Źródło: Allocation and GC Myths (PostScript alert!)

Alokacja Mit 4: Programy niezawierające śmieci powinny zawsze zwalniać całą przydzieloną pamięć.

The Truth: Pominięte zwolnienia w często wykonywanym kodzie powodują wzrost wycieków. Są rzadko akceptowane. ale programy, które zachowują większość przydzielonej pamięci do momentu wyjścia z programu, często działają lepiej bez żadnego pośredniego zwolnienia. Malloc jest znacznie łatwiejszy do wdrożenia, jeśli nie ma za darmo.

W większości przypadków zwalnianie pamięci tuż przed wyjściem z programu jest bezcelowe. System operacyjny i tak go odzyska. Free będzie dotykać i przewijać martwe obiekty; system operacyjny nie.

Konsekwencja: Uważaj na „detektory wycieków”, które liczą przydziały. Niektóre „wycieki” są dobre!

To powiedziawszy, powinieneś naprawdę spróbować uniknąć wszystkich wycieków pamięci!

Drugie pytanie: twój projekt jest w porządku. Jeśli musisz coś zapisać do momentu zamknięcia aplikacji, możesz to zrobić z dynamicznym przydzielaniem pamięci. Jeśli nie znasz wymaganego rozmiaru z góry, nie możesz użyć pamięci przydzielonej statycznie.

współdziałać
źródło
3
Może dlatego, że pytanie, które czytam, dotyczy tego, co dzieje się z wyciekającą pamięcią, a nie czy ten konkretny przykład jest w porządku. Nie głosowałbym jednak za tym, ponieważ to wciąż dobra odpowiedź.
DevinB
3
Prawdopodobnie istniały (wczesne Windows, wczesne Mac OS), a być może nadal, systemy operacyjne, które wymagają procesów w celu zwolnienia pamięci przed wyjściem, w przeciwnym razie przestrzeń nie zostanie odzyskana.
Pete Kirkham
Jest całkowicie w porządku, chyba że zależy Ci na fragmentacji pamięci lub jej braku - robisz to za dużo, a wydajność aplikacji zniknie. Oprócz twardych faktów, zawsze przestrzegaj najlepszych praktyk i budowania dobrych nawyków.
NTDLS,
1
Mam zaakceptowaną odpowiedź, która obecnie wynosi około -11, więc nawet nie biega o rekord.
Paul Tomblin
8
Wydaje mi się, że błędem jest wyjaśnienie potrzeby uwolnienia () pamięci, mówiąc „z powodu wykrywacza nieszczelności”. To tak, jakby powiedzieć „musisz jechać powoli po placu zabaw, bo policjanci mogą na ciebie czekać z fotoradarem”.
Sebastian Mach
57

=== A co z przyszłym proofingiem i ponownym użyciem kodu ? ===

Jeśli nie napiszesz kodu, aby uwolnić obiekty, ograniczasz go do bezpiecznego korzystania z niego tylko wtedy, gdy możesz polegać na zwolnieniu pamięci przez zamknięcie procesu ... tj. Jednorazowe małe użycie projekty lub „wyrzucane” [1] projekty) ... gdzie wiesz, kiedy proces się zakończy.

Jeśli zrobić napisać kod, który free () s całe dynamicznie przydzieloną pamięć, to jesteś w przyszłości pokryć kodu i pozwalając inni używają go w większym projekcie.


[1] w odniesieniu do projektów „wyrzucanych”. Kod używany w projektach „Wyrzucanie” ma sposób, aby nie zostać wyrzuconym. Następną rzeczą, którą wiesz, że minęło dziesięć lat, a Twój kod „wyrzucania” jest nadal używany).

Słyszałem historię o facecie, który napisał kod tylko dla zabawy, aby jego sprzęt działał lepiej. Powiedział „ po prostu hobby, nie będzie duże i profesjonalne ”. Wiele lat później wiele osób korzysta z jego kodu „hobby”.

Trevor Boyd Smith
źródło
8
Doceniony za „małe projekty”. Istnieje wiele dużych projektów, które bardzo celowo nie zwalniają pamięci przy wyjściu, ponieważ znanie platform docelowych jest stratą czasu. IMO, dokładniejszym przykładem byłyby „pojedyncze projekty”. Na przykład, jeśli tworzysz bibliotekę wielokrotnego użytku, która będzie zawarta w innych aplikacjach, nie ma dobrze zdefiniowanego punktu wyjścia, więc nie powinieneś przeciekać pamięci. W przypadku samodzielnej aplikacji zawsze będziesz wiedział dokładnie, kiedy proces się kończy, i możesz podjąć świadomą decyzję o przeniesieniu czyszczenia do systemu operacyjnego (który musi wykonać kontrole w obu kierunkach).
Dan Bechard
Wczorajsza aplikacja jest dzisiejszą funkcją biblioteki, a jutro zostanie połączona z trwałym serwerem, który wywołuje ją tysiące razy.
Adrian McCarthy
1
@AdrianMcCarthy: Jeśli funkcja sprawdzi, czy wskaźnik statyczny ma wartość NULL, zainicjuje go, malloc()jeśli tak jest, i zakończy działanie, jeśli wskaźnik nadal będzie pusty, taka funkcja może być bezpiecznie użyta dowolną liczbę razy, nawet jeśli freenigdy nie zostanie wywołana. Myślę, że prawdopodobnie warto rozróżnić wycieki pamięci, które mogą zużyć nieograniczoną ilość pamięci, w porównaniu z sytuacjami, które mogą zmarnować tylko ograniczoną i przewidywalną ilość pamięci.
supercat
@supercat: Mój komentarz mówił o zmianie kodu w czasie. Jasne, przeciek ograniczonej ilości pamięci nie stanowi problemu. Ale pewnego dnia ktoś będzie chciał zmienić tę funkcję, aby przestała używać statycznego wskaźnika. Jeśli kod nie przewiduje możliwości zwolnienia pamięci wskazanej, będzie to trudna zmiana (lub, co gorsza, zmiana będzie zła, a skończy się to nieograniczonym wyciekiem).
Adrian McCarthy
1
@AdrianMcCarthy: Zmiana kodu, aby nie używać już wskaźnika statycznego, prawdopodobnie wymagałaby przeniesienia wskaźnika do jakiegoś obiektu „kontekstowego” oraz dodania kodu w celu utworzenia i zniszczenia takich obiektów. Jeśli wskaźnik jest zawsze, nullgdy nie istnieje alokacja, a gdy alokacja nie istnieje, wartość różna od wartości zerowej, posiadanie kodu bez alokacji i ustawianie wskaźnika w nullprzypadku zniszczenia kontekstu byłoby proste, szczególnie w porównaniu ze wszystkim innym, co należałoby zrobić aby przenieść obiekty statyczne do struktury kontekstowej.
supercat
52

Masz rację, nic się nie dzieje i szybsze jest wyjście

Istnieją różne przyczyny tego:

  • Wszystkie środowiska stacjonarne i serwerowe po prostu zwalniają całą przestrzeń pamięci przy exit (). Nie są świadomi wewnętrznych struktur danych programu, takich jak stosy.

  • Prawie wszystkie free()implementacje i tak nigdy nie zwracają pamięci do systemu operacyjnego.

  • Co ważniejsze, jest to strata czasu, gdy jest wykonywana tuż przed wyjściem (). Przy wyjściu strony pamięci i przestrzeń wymiany są po prostu zwalniane. Natomiast seria bezpłatnych wywołań () spowoduje spalenie czasu procesora i może spowodować operacje stronicowania dysku, brak pamięci podręcznej i wykluczenie pamięci podręcznej.

Odnośnie możliwości przyszłego ponownego wykorzystania kodu uzasadniającego pewność bezcelowych operacji: to kwestia rozważana, ale prawdopodobnie nie jest to zwinny sposób. YAGNI!

DigitalRoss
źródło
2
Kiedyś pracowałem nad projektem, w którym spędziliśmy krótki czas próbując zrozumieć wykorzystanie pamięci programów (byliśmy zobowiązani do jego obsługi, nie napisaliśmy tego). Opierając się na doświadczeniu, anegdotycznie zgadzam się z twoją drugą kulą. Chciałbym jednak usłyszeć od ciebie (lub kogoś) więcej dowodów na to, że to prawda.
user106740,
3
Nieważne, znalazł odpowiedź: stackoverflow.com/questions/1421491/... . Dziękuję!
user106740,
@aviggiano o nazwie YAGNI.
v.oddou
Zasada YAGNI działa w obie strony: nigdy nie będziesz musiał optymalizować ścieżki wyłączania. Przedwczesne optymalizacje i tak dalej.
Adrian McCarthy
26

Całkowicie nie zgadzam się ze wszystkimi, którzy twierdzą, że OP jest poprawny lub nie ma żadnej szkody.

Wszyscy mówią o nowoczesnym i / lub starszym systemie operacyjnym.

Ale co, jeśli jestem w środowisku, w którym po prostu nie mam systemu operacyjnego? Gdzie nic nie ma?

Wyobraź sobie, że używasz przerwań w stylu wątku i przydzielasz pamięć. W normie C ISO / IEC: 9899 oznacza trwałość pamięci wyrażoną jako:

7.20.3 Funkcje zarządzania pamięcią

1 Kolejność i ciągłość pamięci przydzielanej przez kolejne wywołania funkcji calloc, malloc i realloc nie jest określona. Wskaźnik zwrócony, jeśli alokacja się powiedzie, zostanie odpowiednio wyrównany, aby można go było przypisać do wskaźnika do dowolnego typu obiektu, a następnie użyć do uzyskania dostępu do takiego obiektu lub tablicy takich obiektów w przydzielonym miejscu (do momentu jawnego zwolnienia miejsca) . Żywotność przydzielonego obiektu rozciąga się od przydziału do delokalizacji. [...]

Dlatego nie należy zapominać, że środowisko wykonuje dla ciebie zadanie polegające na uwolnieniu. W przeciwnym razie zostanie dodane do ostatniego zdania: „Lub do czasu zakończenia programu”.

Innymi słowy: nie zwalnianie pamięci to nie tylko zła praktyka. Tworzy nieprzenośny i niezgodny kod C. Które można co najmniej uznać za „poprawne, jeżeli: [...] jest obsługiwane przez środowisko”.

Ale w przypadkach, w których w ogóle nie masz systemu operacyjnego, nikt nie wykonuje zadania za ciebie (wiem, że ogólnie nie przydzielasz i nie przenosisz pamięci w systemach wbudowanych, ale są przypadki, w których możesz chcieć).

Mówiąc ogólnie, zwykły C (jako oznaczony OP), powoduje to po prostu błędny i nieprzenośny kod.

dhein
źródło
4
Kontrargumentem jest to, że jeśli jesteś środowiskiem osadzonym, jako programista byłbyś o wiele bardziej wybredny w zarządzaniu pamięcią. Zwykle chodzi o to, by faktycznie wcześniej wstępnie przydzielić stałą pamięć statyczną, a nie mieć jakiekolwiek mallocady / realloki w czasie wykonywania.
John Go-Soco,
1
@lunarplasma: Chociaż to, co mówisz, nie jest niepoprawne, nie zmienia to faktu, że standard językowy mówi, i każdy, kto działa przeciwko / dalej, nawet jeśli jest to rozsądne, produkuje ograniczony kod. Rozumiem, jeśli ktoś powie „nie muszę się tym przejmować”, ponieważ jest wystarczająca liczba przypadków, w których jest to w porządku. ALE to należy przynajmniej wiedzieć DLACZEGO nie musi się tym przejmować. a zwłaszcza nie pomijaj tego, o ile pytanie nie jest związane z tym szczególnym przypadkiem. A ponieważ OP pyta ogólnie o C w ramach aspektów teoretycznych (szkolnych). Nie można powiedzieć „nie musisz”!
dhein
2
W większości środowisk, w których nie ma systemu operacyjnego, nie ma środków, za pomocą których programy mogłyby się „zakończyć”.
supercat
@ superupat: Jak już pisałem: masz rację. Ale jeśli ktoś o to pyta w związku z przyczynami nauczania i aspektami szkolnymi, nie jest właściwe powiedzenie „Nie musisz o tym myśleć przez większość czasu, nie ma to znaczenia”. Sformułowanie i zachowanie języka definicja jest podana z jakiegoś powodu i tylko dlatego, że większość środowisk obsługuje ją za ciebie, nie możesz powiedzieć, że nie musisz się tym przejmować. To mój punkt.
dhein
2
-1 do cytowania standardu C, podczas gdy większość z nich NIE ma zastosowania w przypadku braku systemu operacyjnego, ponieważ nie ma środowiska wykonawczego zapewniającego funkcje standardowych mandatów, szczególnie w zakresie zarządzania pamięcią i standardowych funkcji bibliotecznych (których oczywiście nie ma również wraz ze środowiskiem uruchomieniowym / systemem operacyjnym).
23

Zazwyczaj zwalniam każdy przydzielony blok, gdy mam pewność, że już z nim skończyłem. Dzisiaj może być punktem wejścia mojego programu main(int argc, char *argv[]), ale jutro może być foo_entry_point(char **args, struct foo *f)i napisany jako wskaźnik funkcji.

Więc jeśli tak się stanie, mam teraz wyciek.

Jeśli chodzi o twoje drugie pytanie, jeśli mój program pobierze dane wejściowe jak a = 5, przydzieliłbym miejsce dla a lub ponownie przydzieliłbym to samo miejsce dla kolejnego a = "foo". Zostanie to przydzielone do:

  1. Użytkownik wpisał „unset a”
  2. Wprowadzono moją funkcję czyszczenia, obsługującą sygnał lub użytkownik wpisał „quit”

Nie mogę wymyślić żadnego nowoczesnego systemu operacyjnego, który nie odzyska pamięci po zakończeniu procesu. Z drugiej strony free () jest tanie, dlaczego nie posprzątać? Jak powiedzieli inni, narzędzia takie jak valgrind świetnie nadają się do wykrywania wycieków, o które naprawdę musisz się martwić. Mimo że przykładowe bloki zostałyby oznaczone jako „wciąż osiągalne”, to tylko dodatkowy szum na wyjściu, gdy próbujesz upewnić się, że nie ma wycieków.

Kolejny mit brzmi: „ Jeśli jest w main (), nie muszę go zwalniać ”, jest to niepoprawne. Rozważ następujące:

char *t;

for (i=0; i < 255; i++) {
    t = strdup(foo->name);
    let_strtok_eat_away_at(t);
}

Jeśli miało to miejsce przed rozwidleniem / demonizacją (i teoretycznie działało wiecznie), twój program przeciekł nieokreślony rozmiar t 255 razy.

Dobry, dobrze napisany program powinien zawsze po sobie posprzątać. Zwolnij całą pamięć, opróżnij wszystkie pliki, zamknij wszystkie deskryptory, odłącz wszystkie pliki tymczasowe itp. Ta funkcja czyszczenia powinna zostać uruchomiona po normalnym zakończeniu lub po otrzymaniu różnego rodzaju sygnałów krytycznych, chyba że chcesz zostawić jakieś pliki, abyś mógł wykryć awarię i wznowić.

Naprawdę, bądź dobry dla biednej duszy, która musi utrzymywać twoje rzeczy, kiedy przechodzisz do innych rzeczy ... podaj im „valgrind clean” :)

Tim Post
źródło
1
I tak, kiedyś mój kolega z drużyny powiedział mi: „Nigdy nie muszę dzwonić za darmo () w main ()” <dreszcze>
Tim Post
free() is cheapchyba że masz miliard struktur danych ze złożonymi relacjami, które musisz uwolnić jeden po drugim, przechodzenie przez strukturę danych w celu wypuszczenia wszystkiego może znacznie wydłużyć czas wyłączenia, szczególnie jeśli połowa tej struktury danych jest już stronicowana na dysk, bez żadnych korzyści.
Lie Ryan,
3
@LieRyan Jeśli masz miliard, tak jak dosłownie miliard struktur, zdecydowanie masz inne problemy, które wymagają specjalnego stopnia rozważenia - daleko poza zakresem tej konkretnej odpowiedzi :)
Tim Post
13

Całkowicie w porządku jest pozostawienie pamięci bez pamięci po wyjściu; malloc () przydziela pamięć z obszaru pamięci zwanego „stertą”, a cała sterta procesu jest zwalniana po zakończeniu procesu.

To powiedziawszy, jednym z powodów, dla których ludzie wciąż upierają się, że dobrze jest zwolnić wszystko przed wyjściem, jest to, że debuggery pamięci (np. Valgrind w Linuksie) wykrywają niewyleczone bloki jako wycieki pamięci, a jeśli masz również „prawdziwe” wycieki pamięci, staje się trudniej je dostrzec, jeśli na końcu uzyskasz „fałszywe” wyniki.

Antti Huima
źródło
1
Czy Valgrind nie radzi sobie całkiem dobrze, rozróżniając „wyciek” i „wciąż osiągalny”?
Christoffer,
11
-1 dla „całkowicie w porządku” Złą praktyką kodowania jest pozostawienie przydzielonej pamięci bez jej zwalniania. Gdyby ten kod został wyodrębniony do biblioteki, spowodowałoby to memleaki w każdym miejscu.
DevinB,
5
+1, aby zrekompensować. Zobacz odpowiedź Compie. freew exitczasie uważany za szkodliwy.
R .. GitHub ZATRZYMAJ LÓD
11

Jeśli korzystasz z przydzielonej pamięci, nie robisz nic złego. Staje się to problemem, gdy piszesz funkcje (inne niż główne), które przydzielają pamięć bez zwalniania jej i bez udostępniania jej pozostałej części programu. Następnie program kontynuuje działanie z przydzieloną pamięcią, ale nie ma możliwości korzystania z niego. Twój program i inne uruchomione programy są pozbawione tej pamięci.

Edycja: Nie jest w 100% dokładne stwierdzenie, że inne działające programy są pozbawione tej pamięci. System operacyjny może zawsze pozwolić mu na użycie go kosztem zamiany programu na pamięć wirtualną ( </handwaving>). Chodzi o to, że jeśli twój program zwolni pamięć, której nie używa, wówczas zamiana pamięci wirtualnej jest mniej prawdopodobna.

Bill jaszczurka
źródło
11

Ten kod zwykle działa poprawnie, ale weź pod uwagę problem ponownego użycia kodu.

Być może napisałeś fragment kodu, który nie zwalnia przydzielonej pamięci, jest on uruchamiany w taki sposób, że pamięć jest następnie automatycznie odzyskiwana. Wydaje się w porządku.

Następnie ktoś inny kopiuje Twój fragment do swojego projektu w taki sposób, że jest on wykonywany tysiąc razy na sekundę. Ta osoba ma teraz ogromny wyciek pamięci w swoim programie. Ogólnie niezbyt dobry, zwykle śmiertelny dla aplikacji serwerowej.

Ponowne użycie kodu jest typowe dla przedsiębiorstw. Zwykle firma jest właścicielem całego kodu, który produkują jej pracownicy, i każdy dział może ponownie wykorzystać to, co firma jest właścicielem. Pisząc taki „niewinnie wyglądający” kod, wywołujesz potencjalny ból głowy u innych ludzi. Może cię to zwolnić.

sharptooth
źródło
2
Warto zwrócić uwagę na możliwość nie tylko kopiowania fragmentu, ale także możliwość napisania programu, który wykonał określone działanie po zmodyfikowaniu go w celu wielokrotnego wykonywania. W takim przypadku dobrze byłoby, aby pamięć została przydzielona jeden raz, a następnie wykorzystana wielokrotnie bez uwolnienia, ale przydzielanie i porzucanie pamięci dla każdej akcji (bez jej zwolnienia) może być katastrofalne.
supercat
7

Jaki jest tutaj prawdziwy wynik?

Twój program wyciekł z pamięci. W zależności od systemu operacyjnego może zostać odzyskany.

Większość nowoczesnych komputerów stacjonarnych systemów operacyjnych zrobić odzyskać pamięć wyciekły na zakończenie procesu, co niestety powszechne ignorowanie problemu, ponieważ mogą być postrzegane przez wielu innych odpowiedzi tutaj.)

Ale polegasz na funkcji bezpieczeństwa, na której nie powinieneś polegać, a twój program (lub funkcja) może działać w systemie, w którym to zachowanie powoduje „twardy” wyciek pamięci, następnym razem.

Być może działasz w trybie jądra lub w systemach operacyjnych vintage / embedded, które nie wykorzystują ochrony pamięci jako kompromisu. (MMU zajmują miejsce na matrycę, ochrona pamięci kosztuje dodatkowe cykle procesora i programiści nie muszą zbytnio prosić o posprzątanie po sobie).

Możesz używać i ponownie wykorzystywać pamięć w dowolny sposób, ale pamiętaj, aby zwolnić wszystkie zasoby przed wyjściem.

DevSolar
źródło
5

W podręczniku online OSTEP znajduje się sekcja dla studentów studiów licencjackich na temat systemów operacyjnych, która dokładnie omawia twoje pytanie.

Odpowiednia sekcja to „Zapominanie o zwolnieniu pamięci” w rozdziale API pamięci na stronie 6, który zawiera następujące wyjaśnienie:

W niektórych przypadkach może się wydawać, że nie wywoływanie free () jest uzasadnione. Na przykład twój program jest krótkotrwały i wkrótce się zakończy; w takim przypadku, gdy proces umrze, system operacyjny wyczyści wszystkie przydzielone strony, a zatem nie wystąpi przeciek pamięci. Chociaż to z pewnością „działa” (patrz strona 7), prawdopodobnie jest to zły nawyk do rozwijania się, więc bądź ostrożny przy wyborze takiej strategii

Ten fragment jest w kontekście wprowadzenia koncepcji pamięci wirtualnej. Zasadniczo w tym momencie książki autorzy wyjaśniają, że jednym z celów systemu operacyjnego jest „wirtualizacja pamięci”, to znaczy, aby każdy program mógł uwierzyć, że ma dostęp do bardzo dużej przestrzeni adresowej pamięci.

Za kulisami system operacyjny tłumaczy „adresy wirtualne”, które widzi użytkownik, na rzeczywiste adresy wskazujące na pamięć fizyczną.

Jednak współdzielenie zasobów, takich jak pamięć fizyczna, wymaga od systemu operacyjnego śledzenia, z jakich procesów korzystają. Jeśli proces zostanie zakończony, odzyskanie pamięci procesu w ramach możliwości i celów projektowych systemu operacyjnego będzie możliwe w celu redystrybucji i współdzielenia pamięci z innymi procesami.


EDYCJA: strona wymieniona we fragmencie jest skopiowana poniżej.

Poza tym: DLACZEGO PAMIĘĆ NIE WYCIE SIĘ PO WYJŚCIU Z PROCESU

Kiedy piszesz program krótkotrwały, możesz przydzielić trochę miejsca za pomocą malloc(). Program działa i wkrótce się kończy: czy trzeba wywołać free()kilka razy tuż przed wyjściem? Chociaż nie wydaje się to niewłaściwe, żadna pamięć nie zostanie „utracona” w żadnym prawdziwym sensie. Powód jest prosty: w systemie istnieją dwa poziomy zarządzania pamięcią. Pierwszy poziom zarządzania pamięcią jest wykonywany przez system operacyjny, który przekazuje pamięć procesom podczas ich działania i zabiera ją z powrotem, gdy procesy wychodzą (lub w inny sposób umierają). Drugi poziom zarządzania znajduje się w ramach każdego procesu, na przykład w stercie podczas wywoływania malloc()i free(). Nawet jeśli nie zadzwoniszfree()(i tym samym wyciek pamięci w stercie), system operacyjny odzyska całą pamięć procesu (w tym strony kodu, stosu i, stosownie do tego, stosu), gdy program zakończy działanie. Bez względu na stan stosu w przestrzeni adresowej, system operacyjny zabiera wszystkie te strony, gdy proces umiera, zapewniając w ten sposób, że pamięć nie zostanie utracona, mimo że jej nie zwolniłeś.

Dlatego w przypadku programów krótkotrwałych wyciek pamięci często nie powoduje żadnych problemów operacyjnych (choć można uznać to za kiepską formę). Gdy piszesz długo działający serwer (taki jak serwer WWW lub system zarządzania bazą danych, który nigdy się nie kończy), wyciek pamięci jest znacznie większym problemem i ostatecznie doprowadzi do awarii, gdy w aplikacji zabraknie pamięci. I oczywiście wyciek pamięci jest jeszcze większym problemem w jednym konkretnym programie: samym systemie operacyjnym. Pokazuje nam jeszcze raz: ci, którzy piszą kod jądra, mają najtrudniejsze zadanie ze wszystkich ...

z strony 7 rozdziału Memory API z

Systemy operacyjne: trzy łatwe utwory
Remzi H. Arpaci-Dusseau i Andrea C. Arpaci-Dusseau Arpaci-Dusseau Books Marzec 2015 (wersja 0.90)

spenceryue
źródło
4

Nie ma realnego niebezpieczeństwa, nie zwalniając zmiennych, ale jeśli przypiszesz wskaźnik do bloku pamięci do innego bloku pamięci bez zwolnienia pierwszego bloku, pierwszy blok nie jest już dostępny, ale nadal zajmuje miejsce. Nazywa się to wyciekiem pamięci, a jeśli robisz to regularnie, proces zacznie zużywać coraz więcej pamięci, zabierając zasoby systemowe innym procesom.

Jeśli proces ten jest krótkotrwały, często możesz tego uniknąć, ponieważ cała przydzielona pamięć jest odzyskiwana przez system operacyjny po zakończeniu procesu, ale radzę nabrać nawyku zwalniania całej pamięci, z której nie będziesz już korzystać.

Kyle Cronin
źródło
1
Chcę powiedzieć -1 w swoim pierwszym stwierdzeniu „nie ma niebezpieczeństwa”, z wyjątkiem tego, że następnie dajesz miłą odpowiedź na pytanie, dlaczego JEST niebezpieczeństwem.
DevinB,
2
W miarę zbliżania się niebezpieczeństw jest to dość łagodne - każdego dnia zrobię wyciek pamięci na segfault.
Kyle Cronin
1
Bardzo prawda i oboje wolelibyśmy
none
2
@KyleCronin Chciałbym dużo raczej mieć segfault niż wyciek pamięci, ponieważ oba są poważne błędy i naruszenia ochrony pamięci są łatwiejsze do wykrycia. Zbyt często wycieki pamięci pozostają niezauważone lub nierozwiązane, ponieważ są „dość łagodne”. Moja pamięć RAM i ja całkowicie się nie zgadzamy.
Dan Bechard,
@Dan Oczywiście jako programista. Jako użytkownik zajmę się wyciekiem pamięci. Wolałbym mieć oprogramowanie, które działa, choć z przeciekającą pamięcią, zamiast oprogramowania, które nie działa.
Kyle Cronin
3

Masz absolutną rację pod tym względem. W małych, trywialnych programach, w których zmienna musi istnieć aż do śmierci programu, nie ma realnej korzyści z zwolnienia pamięci.

W rzeczywistości byłem kiedyś zaangażowany w projekt, w którym każde wykonanie programu było bardzo złożone, ale stosunkowo krótkotrwałe, i postanowiłem po prostu zachować przydzieloną pamięć i nie destabilizować projektu, popełniając błędy przy jego dezalokacji.

To powiedziawszy, w większości programów nie jest to tak naprawdę opcja, lub może doprowadzić do wyczerpania się pamięci.

Uri
źródło
2

Masz rację, pamięć jest automatycznie zwalniana po zakończeniu procesu. Niektóre osoby starają się nie przeprowadzać obszernego czyszczenia po zakończeniu procesu, ponieważ wszystko to zostanie przekazane systemowi operacyjnemu. Jednak podczas działania programu należy zwolnić nieużywaną pamięć. Jeśli tego nie zrobisz, możesz w końcu skończyć się lub spowodować nadmierne stronicowanie, jeśli zestaw roboczy stanie się zbyt duży.

Michał
źródło
2

Jeśli tworzysz aplikację od zera, możesz dokonać świadomego wyboru, kiedy zadzwonić za darmo. Twój przykładowy program jest w porządku: alokuje pamięć, być może masz kilka sekund pracy, a następnie zamyka się, uwalniając wszystkie zasoby, o które twierdził.

Jeśli piszesz cokolwiek innego - serwer / długo działającą aplikację lub bibliotekę, z której może korzystać ktoś inny, powinieneś spodziewać się bezpłatnego połączenia ze wszystkim, co robisz malloc.

Ignorując pragmatyczną stronę przez sekundę, znacznie bezpieczniej jest podążać za bardziej rygorystycznym podejściem i zmusić się do uwolnienia wszystkiego, co Malloc. Jeśli nie masz zwyczaju obserwowania wycieków pamięci za każdym razem, gdy kodujesz, możesz z łatwością wygenerować kilka wycieków. Innymi słowy, tak - możesz uciec bez niego; proszę jednak uważać.

ojrac
źródło
0

Jeśli program zapomni zwolnić kilka megabajtów przed wyjściem, system operacyjny je zwolni. Ale jeśli twój program działa przez kilka tygodni, a pętla wewnątrz programu zapomina o zwolnieniu kilku bajtów w każdej iteracji, będziesz mieć ogromny wyciek pamięci, który pochłonie całą dostępną pamięć w komputerze, chyba że uruchomisz ją ponownie base => nawet małe wycieki pamięci mogą być złe, jeśli program jest używany do poważnie dużego zadania, nawet jeśli pierwotnie nie został zaprojektowany dla jednego.

Gunter Königsmann
źródło
-2

Myślę, że twoje dwa przykłady są w rzeczywistości tylko jednym: free()powinny wystąpić tylko na końcu procesu, co, jak zauważyłeś, jest bezużyteczne, ponieważ proces się kończy.

W drugim przykładzie jedyną różnicą jest to, że dopuszczasz nieokreśloną liczbę malloc(), co może doprowadzić do wyczerpania się pamięci. Jedynym sposobem na poradzenie sobie z sytuacją jest sprawdzenie kodu powrotu malloc()i odpowiednie działanie.

mouviciel
źródło