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 malloc
wywoł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? free
Uważ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 = 123
a 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, malloc
ponieważ 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?
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.Odpowiedzi:
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.
źródło
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!)
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.
źródło
=== 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”.
źródło
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ślifree
nigdy 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.null
gdy nie istnieje alokacja, a gdy alokacja nie istnieje, wartość różna od wartości zerowej, posiadanie kodu bez alokacji i ustawianie wskaźnika wnull
przypadku 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.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!
źródło
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:
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.
źródło
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:
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:
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” :)
źródło
free() is cheap
chyba ż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.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.
źródło
free
wexit
czasie uważany za szkodliwy.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.źródło
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ć.
źródło
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.
źródło
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:
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.
źródło
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ć.
źródło
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.
źródło
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.
źródło
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ć.
źródło
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.
źródło
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 powrotumalloc()
i odpowiednie działanie.źródło