Jak daleko mogą zajść wycieki pamięci?

118

Wiele razy miałem wycieki pamięci. Zwykle kiedy - mallocjakby nie było jutra, albo wiszące FILE *jak brudne pranie. Ogólnie zakładam (czytaj: mam nadzieję), że cała pamięć jest czyszczona przynajmniej po zakończeniu działania programu. Czy są jakieś sytuacje, w których wyciekająca pamięć nie zostanie zebrana po zakończeniu działania programu lub awarii?

Jeśli odpowiedź różni się znacznie w zależności od języka, skupmy się na C (++).

Zwróć uwagę na hiperboliczne użycie wyrażenia „jakby nie było jutra” i „dyndające ... jak brudne pranie”. Niebezpieczne * malloc* może zranić tych, których kochasz. Należy również zachować ostrożność przy brudnym praniu.

DilithiumMatrix
źródło
3
Jeśli używasz „nowoczesnego” systemu operacyjnego, takiego jak Linux lub Windows, sam system operacyjny rozwiąże każdą niewykorzystaną pamięć po zakończeniu działania programu.
Oliver Charlesworth,
60
Zamiast oszukiwać, jakby nie było jutra, spróbuj udawać, że jest jutro i śledź swoją pamięć!
William Pursell,
8
@WilliamPursell ah, więc mówisz, że nie powinno callocbyć jutra. Doskonały.
DilithiumMatrix
8
„Jeśli odpowiedź różni się znacznie w poszczególnych językach, skupmy się na c (++)”. c i c ++ nie są tym samym językiem!
Johnsyweb
11
@zhermes: Komentarz dotyczący różnych języków C i C ++ ukrywa więcej niż myślisz ... W C ++ wolisz korzystać z obiektów z automatycznym czasem przechowywania, postępuj zgodnie z idiomem RAII ... pozwalasz tym obiektom zająć się pamięcią zarządzanie dla Ciebie.
LihO

Odpowiedzi:

111

Nie. Systemy operacyjne zamykają wszystkie zasoby przechowywane przez procesy.

Dotyczy to wszystkich zasobów, które utrzymuje system operacyjny: pamięci, otwartych plików, połączeń sieciowych, uchwytów okien ...

To powiedziawszy, jeśli program działa w systemie wbudowanym bez systemu operacyjnego lub z bardzo prostym lub błędnym systemem operacyjnym, pamięć może być bezużyteczna do czasu ponownego uruchomienia. Ale gdybyś był w takiej sytuacji, prawdopodobnie nie zadawałbyś tego pytania.

Zwolnienie niektórych zasobów przez system operacyjny może zająć dużo czasu. Na przykład port TCP używany przez serwer sieciowy do akceptowania połączeń może zająć kilka minut, zanim zostanie zwolniony, nawet jeśli zostanie poprawnie zamknięty przez program. Program sieciowy może również przechowywać zdalne zasoby, takie jak obiekty bazy danych. System zdalny powinien zwolnić te zasoby w przypadku utraty połączenia sieciowego, ale może to potrwać nawet dłużej niż lokalny system operacyjny.

Joni
źródło
5
Powszechnym paradygmatem w systemach RTOS jest model jednoprocesowy, wielowątkowy i brak ochrony pamięci między „zadaniami”. Zwykle jest jeden stos. Z pewnością tak działało VxWorks - i prawdopodobnie nadal działa.
marko
29
Należy pamiętać, że nie wszystkie zasoby mogą zostać zwolnione przez system operacyjny. Połączenia sieciowe, transakcje w bazach danych itp., Jeśli ich nie zamykasz jawnie, może powodować niepożądane skutki. Niezamknięcie połączenia sieciowego może spowodować, że serwer pomyśli, że nadal jesteś aktywny przez nieokreślony czas, a w przypadku serwerów, które ograniczają liczbę aktywnych połączeń, może przypadkowo spowodować odmowę usługi. Niezamknięcie transakcji bazy danych może spowodować utratę niezatwierdzonych danych.
Lie Ryan
1
@Marko: Najnowsza wersja vxWorks obsługuje teraz RTP (procesy czasu rzeczywistego), które obsługują ochronę pamięci.
Xavier T.
20
„Systemy operacyjne zwalniają wszystkie zasoby przetrzymywane przez procesy po ich zamknięciu”. Nie do końca prawda. Na przykład w (przynajmniej) Linuksie semafory SysV i inne obiekty IPC nie są czyszczone przy zakończeniu procesu. Dlatego istnieje ipcrmmożliwość ręcznego czyszczenia linux.die.net/man/8/ipcrm .
sleske
7
Ponadto, jeśli obiekt ma tymczasowy plik, że utrzymuje on, że oczywiście nie będzie się umyć potem.
Mooing Duck
47

Standard C nie określa, że ​​pamięć przydzielona przez mallocjest zwalniana po zakończeniu programu. Jest to wykonywane przez system operacyjny, a nie wszystkie systemy operacyjne (zazwyczaj są to systemy wbudowane), zwalniają pamięć po zakończeniu działania programu.

ouah
źródło
20
Dzieje się tak mniej więcej dlatego, że standard C mówi o programach w C, a nie o systemach operacyjnych, na których C akurat działa ...
vonbrand
5
@vonbrand Standard C mógł mieć akapit, który mówi, że kiedy mainzwraca, malloczwalniana jest cała pamięć przydzielona przez . Na przykład mówi, że wszystkie otwarte pliki są zamykane przed zakończeniem programu. W przypadku pamięci przydzielonej my mallocpo prostu nie jest określony. Teraz, oczywiście, moje zdanie dotyczące systemu operacyjnego opisuje to, co zwykle się robi, a nie to, co zaleca norma, ponieważ nie określa niczego na ten temat.
ouah
Poprawię mój komentarz: standard mówi o C, a nie o tym, jak program jest uruchamiany i zatrzymywany. Możesz bardzo dobrze napisać program w C, który działa bez systemu operacyjnego. W takim przypadku nie ma nikogo, kto zrobi porządki. Średnia bardzo celowo nie podaje nic chyba potrzebne, tak aby nie utrudniały zastosowań bez potrzeby.
vonbrand
2
@ouah: " kiedy main zwraca ...". To założenie. Musimy rozważyć „ jeśli główne zwroty…”. std::atexitrozważa również zakończenie programu za pośrednictwem std::exit, a następnie jest również std::aborti (specyficzne dla C ++) std::terminate.
MSalters
@ouah: Gdyby to zostało uwzględnione, atexitnie byłoby użyteczne. :-)
R .. GitHub STOP HELPING ICE
28

Ponieważ wszystkie odpowiedzi obejmowały większość aspektów twojego pytania we współczesnych systemach operacyjnych, ale historycznie jest jeden, o którym warto wspomnieć, jeśli kiedykolwiek programowałeś w świecie DOS. Programy rezydenta terminanta i pobytu (TSR) zwykle zwracałyby kontrolę do systemu, ale rezydowałyby w pamięci, która mogłaby zostać przywrócona przez przerwanie programowe / sprzętowe. Podczas pracy na tych systemach operacyjnych pojawiały się komunikaty typu „brak pamięci! Spróbuj wyładować niektóre programy TSR” .

Z technicznego punktu widzenia program się kończy , ale ponieważ nadal rezyduje w pamięci, żaden wyciek pamięci nie zostałby zwolniony, chyba że wyładujesz program.

Możesz więc uznać to za inny przypadek, z wyjątkiem systemów operacyjnych, które nie odzyskują pamięci, ponieważ jest wadliwy lub ponieważ wbudowany system operacyjny jest do tego zaprojektowany.

Pamiętam jeszcze jeden przykład. Customer Information Control System (CICS), serwer transakcji, który działa głównie na komputerach mainframe IBM, jest pseudo-konwersacyjny. Po wykonaniu przetwarza dane wprowadzone przez użytkownika, generuje inny zestaw danych dla użytkownika, przesyłając je do węzła końcowego użytkownika i kończy pracę. Po aktywacji klawisza uwagi ponownie ożywia się, aby przetworzyć inny zestaw danych. Ze względu na sposób, w jaki zachowuje się, ponownie pod względem technicznym, system operacyjny nie odzyska pamięci z zakończonych programów CICS, chyba że ponownie uruchomisz serwer transakcji CICS.

Abhijit
źródło
To naprawdę interesujące, dzięki za notatkę historyczną! Czy wiesz, czy ten paradygmat wynikał z tego, że zwalnianie pamięci było zbyt kosztowne obliczeniowo, gdyby nie było to konieczne? A może po prostu nigdy nie pomyślano o alternatywie?
DilithiumMatrix
1
@zhermes: Było to niemożliwe obliczeniowo, ponieważ DOS po prostu nie śledził alokacji pamięci dla TSR. Prawie z definicji: celem było pozostanie rezydentem . Jeśli chcesz, aby Twój TSR zwolnił część, ale nie całą pamięć, decyzja, co zwolnić, należała do Ciebie.
MSalters
2
@zhermes: DOS (podobnie jak CP / M, jego przodek) nie był tym, co nazwalibyście systemem operacyjnym w nowoczesnym tego słowa znaczeniu. W rzeczywistości był to zbiór narzędzi we / wy, które można było wywołać w standardowy sposób, w pakiecie z procesorem poleceń, który pozwoliłby uruchomić jeden program na raz. Nie istniało pojęcie o procesach, a pamięć nie była ani wirtualna, ani chroniona. TSR były użytecznym hakiem, który mógł powiedzieć systemowi, że zajmują do 64 KB miejsca i podłączał się do przerwań, aby zostali wezwani.
Blrfl
8

Jak powiedzieli inni, większość systemów operacyjnych odzyskuje przydzieloną pamięć po zakończeniu procesu (i prawdopodobnie inne zasoby, takie jak gniazda sieciowe, uchwyty plików itp.).

Powiedziawszy to, pamięć może nie być jedyną rzeczą, o którą musisz się martwić, gdy masz do czynienia z nowym / usuń (zamiast surowego malloc / free). Pamięć przydzielona w new może zostać odzyskana, ale rzeczy, które można zrobić w destruktorach obiektów, nie będą miały miejsca. Być może destruktor jakiejś klasy zapisuje wartość wartowniczą do pliku po zniszczeniu. Jeśli proces właśnie się zakończy, uchwyt pliku może zostać opróżniony, a pamięć odzyskana, ale ta wartość wartownicza nie zostanie zapisana.

Morał z tej historii, zawsze posprzątaj po sobie. Nie pozwól, by coś się zwisało. Nie polegaj na czyszczeniu systemu operacyjnego po tobie. Posprzątaj po sobie.

Andre Kostur
źródło
Nie polegaj na tym, że system operacyjny wyczyści po tobie. Posprzątaj po sobie.' Często jest to trudne ... „bardzo, bardzo trudne” w przypadku złożonych aplikacji wielowątkowych. Rzeczywiste wycieki, w których wszystkie odniesienia do zasobu zostały utracone, są złe. Zezwolenie na oczyszczenie systemu operacyjnego zamiast jawnego zwolnienia odniesień nie zawsze jest złe i często jest jedynym rozsądnym rozwiązaniem.
Martin James,
1
W C ++, destruktory będą wywoływane po zakończeniu programu (chyba że kill -9pojawi się jakiś mniej niż błyskotliwy fan ...)
vonbrand
@vonbrand Prawda, ale jeśli mówimy o wyciekach z dynamicznymi obiektami, te destruktory nie wystąpią. Obiekt wychodzący poza zasięg jest surowym wskaźnikiem, a jego destruktor nie działa. (Oczywiście zobacz obiekty RAII, aby złagodzić ten problem ...)
Andre Kostur
1
Problem z RAII polega na tym, że nalega na zwalnianie obiektów przy wyjściu procesu, których w rzeczywistości nie jest ważne, aby się ich pozbyć. Połączenia DB, z którymi chcesz być ostrożny, ale ogólna pamięć jest najlepiej czyszczona przez system operacyjny (wykonuje znacznie lepszą pracę). Problem objawia się jako program, którego zakończenie zajmuje absolutnie wieki, gdy ilość pamięci, która została wyświetlona, ​​wzrośnie. To również nietrywialne rozwiązanie…
Donal Fellows
@vonbrand: To nie jest takie proste. std::exitwezwie lekarzy, std::abortnie zrobi, niechętne wyjątki mogą.
MSalters
7

Zależy to raczej od systemu operacyjnego niż języka. Ostatecznie każdy program w dowolnym języku otrzyma swoją pamięć z systemu operacyjnego.

Nigdy nie słyszałem o systemie operacyjnym, który nie odzyskuje pamięci, gdy program kończy pracę / ulega awarii. Więc jeśli twój program ma górną granicę pamięci, którą musi przydzielić, to samo przydzielanie i nigdy nie zwalnianie jest całkowicie uzasadnione.

Jan
źródło
Czy mógłbyś zepsuć obraz pamięci jądra w przypadku uproszczonego systemu operacyjnego? .. Na przykład te systemy operacyjne bez nawet wielozadaniowości.
ulidtko
@ulidtko, to będzie przykręcić rzeczy. Jeśli mój program wymaga, powiedzmy, 1GiB od czasu do czasu i przechwytuje to na czas, odmawia innym korzystania z tych zasobów, nawet jeśli ich nie używają. To może mieć znaczenie dzisiaj, albo nie. Ale środowisko będzie radykalnie zmienić. Gwarantowane.
vonbrand
@vonbrand Rzadkie użycie 1GiB normalnie nie stanowi problemu (o ile masz dużo pamięci fizycznej), ponieważ nowoczesne systemy operacyjne mogą stronicować bity, które nie są obecnie aktywne. Problem pojawia się, gdy w aktywnym użyciu jest więcej pamięci wirtualnej niż fizycznej, w której można ją hostować.
Donal Fellows,
5

Jeśli program zostanie kiedykolwiek przekształcony w komponent dynamiczny („wtyczkę”), który jest ładowany do przestrzeni adresowej innego programu, będzie to kłopotliwe, nawet w systemie operacyjnym z uporządkowanym zarządzaniem pamięcią. Nie musimy nawet myśleć o przeniesieniu kodu do mniej wydajnych systemów.

Z drugiej strony zwolnienie całej pamięci może wpłynąć na wydajność czyszczenia programu.

Jeden program, nad którym pracowałem, wymagał 30 sekund lub więcej, aby program się zakończył, ponieważ powtarzał się przez wykres całej pamięci dynamicznej i zwalniał ją kawałek po kawałku.

Rozsądnym rozwiązaniem jest posiadanie tam możliwości i objęcie ich przypadkami testowymi, ale wyłączenie ich w kodzie produkcyjnym, aby aplikacja szybko kończyła pracę.

Kaz
źródło
5

Wszystkie systemy operacyjne zasługujące na ten tytuł posprzątają bałagan spowodowany przez proces po zakończeniu. Ale zawsze zdarzają się nieprzewidziane zdarzenia, co by było, gdyby jakoś odmówiono mu dostępu, a jakiś biedny programista nie przewidział takiej możliwości i nie próbuje ponownie trochę później? Zawsze bezpieczniej jest po prostu oczyścić się, JEŚLI wycieki pamięci są krytyczne - w przeciwnym razie nie jest to naprawdę warte wysiłku IMO, jeśli wysiłek ten jest kosztowny.

Edycja: musisz wyczyścić wycieki pamięci, jeśli znajdują się w miejscu, w którym będą się gromadzić, jak w pętlach. Wycieki pamięci, o których mówię, to te, które narastają w ciągłym czasie w trakcie programu, jeśli masz wyciek jakiegokolwiek innego rodzaju, najprawdopodobniej wcześniej czy później będzie to poważny problem.

Z technicznego punktu widzenia, jeśli wycieki mają „złożoność” pamięci O (1), w większości przypadków są w porządku, O (logn) już nieprzyjemne (aw niektórych przypadkach śmiertelne) i O (N) + nie do zniesienia.


źródło
3

Pamięć współdzielona w systemach zgodnych z POSIX utrzymuje się do momentu wywołania shm_unlink lub ponownego uruchomienia systemu.

klearn
źródło
2

Jeśli masz komunikację międzyprocesową, może to prowadzić do tego, że inne procesy nigdy nie będą kończyć i zużywać zasobów w zależności od protokołu.

Dla przykładu, kiedyś eksperymentowałem z drukowaniem na drukarce PDF w Javie, kiedy zamykałem JVM w środku zadania drukarki, proces buforowania PDF pozostał aktywny i musiałem go zabić w menedżerze zadań, zanim mogłem ponów próbę drukowania.

maniak grzechotki
źródło