W wielu książkach i samouczkach słyszałem, jak zaakcentowano praktykę zarządzania pamięcią i czułem, że wydarzyłyby się jakieś tajemnicze i okropne rzeczy, gdybym nie zwolnił pamięci po jej użyciu.
Nie mogę mówić za innymi systemami (chociaż dla mnie rozsądnie jest założyć, że stosują one podobną praktykę), ale przynajmniej w systemie Windows jądro ma zasadniczo gwarancję wyczyszczenia większości zasobów (z wyjątkiem kilku nieparzystych) używanych przez program po zakończeniu programu. Który obejmuje między innymi pamięć sterty.
Rozumiem, dlaczego chcesz zamknąć plik po zakończeniu korzystania z niego, aby udostępnić go użytkownikowi lub dlaczego chcesz odłączyć gniazdo podłączone do serwera w celu zaoszczędzenia przepustowości, ale wydaje się głupie muszę zarządzać Całą pamięcią używaną przez program.
Teraz zgadzam się, że to pytanie jest szerokie, ponieważ sposób, w jaki powinieneś obchodzić się z pamięcią, zależy od tego, ile pamięci potrzebujesz i kiedy jej potrzebujesz, więc zawęzię zakres tego pytania do tego: jeśli potrzebuję użyć pamięć przez cały okres użytkowania mojego programu, czy naprawdę konieczne jest zwolnienie go tuż przed zakończeniem programu?
Edycja: Pytanie zasugerowane jako duplikat dotyczyło rodziny systemów operacyjnych Unix. Jego najwyższa odpowiedź określa nawet narzędzie specyficzne dla Linuksa (np. Valgrind). To pytanie ma na celu objąć większość „normalnych” niewbudowanych systemów operacyjnych i dlaczego jest lub nie jest dobrą praktyką zwalnianie pamięci, która jest potrzebna przez cały okres życia programu.
źródło
Odpowiedzi:
Nie jest to obowiązkowe, ale może mieć zalety (jak również pewne wady).
Jeśli program przydzieli pamięć raz podczas jej wykonywania, a w przeciwnym razie nigdy nie zwolni jej do czasu zakończenia procesu, rozsądnym rozwiązaniem może być nie zwalnianie pamięci ręcznie i poleganie na systemie operacyjnym. W każdym nowoczesnym systemie operacyjnym, jaki znam, jest to bezpieczne, pod koniec procesu cała przydzielona pamięć jest niezawodnie zwracana do systemu.
W niektórych przypadkach nieoczyszczanie przydzielonej pamięci w sposób jawny może być nawet znacznie szybsze niż czyszczenie.
Jednak poprzez jawne zwolnienie całej pamięci pod koniec wykonywania,
Żywotność programów może ulec zmianie. Być może twój program jest dzisiaj małym narzędziem wiersza poleceń, którego typowy czas życia wynosi mniej niż 10 minut, i przydziela pamięć w częściach około KB co 10 sekund - więc nie trzeba w ogóle zwalniać przydzielonej pamięci przed zakończeniem programu. Później program został zmieniony i zyskuje przedłużone użycie w ramach procesu serwera trwającego kilka tygodni - więc nie zwalnianie nieużywanej pamięci pomiędzy nie jest już opcją, w przeciwnym razie program zacznie zużywać całą dostępną pamięć serwera . Oznacza to, że będziesz musiał przejrzeć cały program, a następnie dodać kod zwolnienia. Jeśli masz szczęście, jest to łatwe zadanie, jeśli nie, może być tak trudne, że szanse są duże, że przegapisz miejsce. A kiedy będziesz w takiej sytuacji, żałujesz, że nie dodałeś „darmowego”
Mówiąc bardziej ogólnie, pisanie kodu przydzielającego i związanego z nim zwolnienia przydziału zawsze jest liczone jako „dobry nawyk” wśród wielu programistów: robiąc to zawsze, zmniejszasz prawdopodobieństwo zapomnienia kodu zwolnienia w sytuacjach, w których pamięć musi zostać zwolniona.
źródło
main
. W.Zwolnienie pamięci pod koniec działania programu to tylko strata czasu procesora. To jest jak sprzątanie domu przed wypuszczeniem go z orbity.
Czasami jednak to, co było krótko działającym programem, może stać się częścią znacznie dłuższego programu. Wtedy konieczne staje się uwolnienie rzeczy. Jeśli nie zostało to przynajmniej w pewnym stopniu przemyślane, może wymagać znacznej przeróbki.
Jednym sprytnym rozwiązaniem tego problemu jest „talloc”, który pozwala ci dokonać mnóstwa przydziałów pamięci, a następnie wyrzucić je wszystkie za pomocą jednego połączenia.
źródło
free
jest zwykle znacznie szybszy niżmalloc
.Możesz użyć języka z funkcją odśmiecania pamięci (np. Scheme, Ocaml, Haskell, Common Lisp, a nawet Java, Scala, Clojure).
(W większości języków GC-ED, nie ma sposobu, aby wyraźnie i ręcznie wolnej pamięci Czasami niektóre wartości mogą być! Sfinalizowane , np GC i systemu wykonawczego by zamknąć wartość uchwytu pliku, gdy wartość ta jest nieosiągalny, ale to nie jest jasne , niewiarygodne, a zamiast tego należy jawnie zamknąć uchwyty plików, ponieważ finalizacja nigdy nie jest gwarantowana)
Możesz również, dla swojego programu zakodowanego w C (lub nawet C ++) użyć konserwatywnego modułu wyrzucania śmieci Boehm . Można by wtedy wymienić wszystkie swoje
malloc
zeGC_malloc
i nie przejmowaćfree
-ing dowolny wskaźnik. Oczywiście musisz zrozumieć zalety i zalety używania GC Boehm. Przeczytaj także podręcznik GC .Zarządzanie pamięcią jest globalną własnością programu. W pewnym sensie (i żywotność niektórych danych) jest niekompozytowa i niemodularna, ponieważ cała właściwość programu.
W końcu, jak zauważyli inni, jawne jest jawne stosowanie
free
strefy pamięci C przydzielonej do stosu. W przypadku programu zabawkowego, który nie przydziela dużej ilości pamięci, można w ogóle zdecydować się na brakfree
pamięci (ponieważ po zakończeniu procesu jego zasoby, w tym wirtualna przestrzeń adresowa , zostaną zwolnione przez system operacyjny).Nie, nie musisz tego robić. I wiele programów w świecie rzeczywistym nie zadaje sobie trudu, aby zwolnić trochę pamięci, która jest potrzebna przez cały okres życia (w szczególności kompilator GCC nie zwalnia części swojej pamięci). Jednak gdy to zrobisz (np. Nie
free
zawracasz sobie głowy konkretnym fragmentem dynamicznie przydzielanych danych C ), lepiej skomentuj ten fakt, aby ułatwić pracę przyszłym programistom przy tym samym projekcie. Zazwyczaj zalecam, aby ilość pamięci niewyleczonej pozostała ograniczona, i zwykle relatywnie mała wartość wrt do całkowitej używanej pamięci sterty.Zauważ, że system
free
często nie zwalnia pamięci do systemu operacyjnego (np. Wywołując munmap (2) w systemach POSIX), ale zwykle zaznacza strefę pamięci jako nadającą się do ponownego wykorzystania w przyszłościmalloc
. W szczególności wirtualna przestrzeń adresowa (np. Widziana/proc/self/maps
w systemie Linux, patrz proc (5) ....) może się później nie zmniejszaćfree
(stąd narzędzia takie jakps
lubtop
zgłaszają taką samą ilość używanej pamięci dla twojego procesu).źródło
Nie jest to konieczne , ponieważ w przypadku niepowodzenia nie wykonasz poprawnie swojego programu. Istnieją jednak powody, które możesz wybrać, jeśli masz szansę.
Jednym z najpotężniejszych przypadków, na które napotykam (w kółko) jest to, że ktoś pisze mały fragment kodu symulacyjnego, który działa w pliku wykonywalnym. Mówią „chcielibyśmy, aby ten kod został włączony do symulacji”. Pytam ich, jak zamierzają ponownie zainicjować między biegami w Monte Carlo, i patrzą na mnie tępo. „Co oznacza ponowne zainicjowanie? Po prostu uruchamiasz program z nowymi ustawieniami?”
Czasami czyste czyszczenie znacznie ułatwia korzystanie z oprogramowania. W przypadku wielu przykładów zakładasz, że nigdy nie musisz czegoś sprzątać, i przyjmujesz założenia dotyczące sposobu obchodzenia się z danymi i ich żywotnością wokół tych domniemań. Po przejściu do nowego środowiska, w którym te domniemania są nieprawidłowe, całe algorytmy mogą nie działać.
Na przykład, jak dziwne rzeczy mogą się wydarzyć, zobacz, jak zarządzane języki radzą sobie z finalizacją na końcu procesów lub jak C # radzi sobie z programowym zatrzymywaniem domen aplikacji. Zawiązują się w węzły, ponieważ w takich ekstremalnych przypadkach pojawiają się załamania.
źródło
Ignorowanie języków, w których i tak nie zwalniasz pamięci ręcznie ...
To, co teraz nazywasz „programem”, może kiedyś stać się funkcją lub metodą, która jest częścią większego programu. I wtedy ta funkcja może zostać wywołana wiele razy. A potem pamięć, którą powinieneś „uwolnić ręcznie”, będzie przeciekiem pamięci. To oczywiście wezwanie do osądu.
źródło
Ponieważ jest bardzo prawdopodobne, że za jakiś czas będziesz chciał zmienić swój program, być może zintegrować go z czymś innym, uruchomić kilka instancji w sekwencji lub równolegle. Wtedy konieczne będzie ręczne zwolnienie tej pamięci - ale nie będziesz już zapamiętywał okoliczności, a ponowne zrozumienie twojego programu będzie kosztować znacznie więcej czasu.
Rób rzeczy, dopóki twoje zrozumienie na ich temat jest wciąż świeże.
To niewielka inwestycja, która może przynieść duże zyski w przyszłości.
źródło
Nie, nie jest to konieczne, ale to dobry pomysł.
Mówisz, że czujesz, że „wydarzyłyby się tajemnicze i okropne rzeczy, gdybym nie zwolnił pamięci po jej zakończeniu”.
Z technicznego punktu widzenia jedyną konsekwencją tego jest to, że Twój program będzie zjadał więcej pamięci, dopóki nie zostanie osiągnięty twardy limit (np. Wyczerpana zostanie wirtualna przestrzeń adresowa) lub wydajność stanie się nie do przyjęcia. Jeśli program wkrótce się zakończy, nic z tego nie ma znaczenia, ponieważ proces faktycznie przestaje istnieć. „Tajemnicze i straszne rzeczy” dotyczą wyłącznie stanu psychicznego dewelopera. Znalezienie źródła wycieku pamięci może być absolutnym koszmarem (jest to mało powiedziane) i wymaga dużo umiejętności i dyscypliny, aby napisać kod, który nie przecieka. Zalecanym podejściem do rozwijania tej umiejętności i dyscypliny jest zawsze zwalnianie pamięci, gdy nie jest już potrzebne, nawet jeśli program się kończy.
Oczywiście ma to tę dodatkową zaletę, że twój kod może być ponownie użyty i dostosowany łatwiej, jak powiedzieli inni.
Istnieje jednak co najmniej jeden przypadek, w którym lepiej nie zwalniać pamięci tuż przed zakończeniem programu.
Rozważ przypadek, w którym dokonałeś milionów małych alokacji, a większość z nich została zamieniona na dysk. Kiedy zaczniesz zwalniać wszystko, większość pamięci musi zostać zamieniona z powrotem do pamięci RAM, aby można było uzyskać dostęp do informacji księgowych, tylko po to, aby natychmiast usunąć dane. Może to spowodować, że program zajmie kilka minut, aby wyjść! Nie wspominając o tym, że w tym czasie wywierał duży nacisk na dysk i pamięć fizyczną. Jeśli na początku brakuje pamięci fizycznej (być może program jest zamykany, ponieważ inny program żuje dużo pamięci), poszczególne strony mogą wymagać zamiany kilka razy, gdy kilka obiektów musi zostać uwolnionych z ta sama strona, ale nie są one zwalniane kolejno.
Jeśli zamiast tego program po prostu przerwie, system operacyjny po prostu odrzuci całą pamięć, która została zamieniona na dysk, co jest prawie natychmiastowe, ponieważ nie wymaga żadnego dostępu do dysku.
Ważne jest, aby pamiętać, że w języku OO wywołanie destruktora obiektu spowoduje również zamianę pamięci; jeśli musisz to zrobić, równie dobrze możesz zwolnić pamięć.
źródło
Główne powody ręcznego czyszczenia to: mniej prawdopodobne jest, że pomylisz autentyczny wyciek pamięci z blokiem pamięci, który zostanie wyczyszczony po wyjściu, czasami złapiesz błędy w alokatorze tylko wtedy, gdy przejdziesz całą strukturę danych do zwalnianie go, a będziesz miał znacznie mniejszy ból głowy, jeśli kiedykolwiek byłaby tak że część obiektu będzie trzeba się zwalniane przed zakończeniem programu.
Główne powody, dla których nie należy czyścić ręcznie, to: wydajność, wydajność, wydajność oraz możliwość wystąpienia błędu typu „dwa razy za darmo” lub „użyj po zwolnieniu” w niepotrzebnym kodzie czyszczenia, który może zmienić się w błąd po awarii lub bezpieczeństwo wykorzystać.
Jeśli zależy Ci na wydajności, zawsze chcesz profilować, aby dowiedzieć się, gdzie marnujesz cały swój czas, zamiast zgadywać. Możliwym kompromisem jest zawinięcie opcjonalnego kodu czyszczącego w bloku warunkowym, pozostawienie go podczas debugowania, aby uzyskać korzyści z pisania i debugowania kodu, a następnie, jeśli tylko empirycznie stwierdzono, że jest to zbyt wiele narzut, powiedz kompilatorowi, aby pomijał go w końcowym pliku wykonywalnym. A opóźnienie zamknięcia programu prawie z definicji prawie nigdy nie znajduje się na ścieżce krytycznej.
źródło