Ogólne wskazówki dotyczące unikania wycieków pamięci w C ++ [zamknięte]


131

Jakie są ogólne wskazówki, które pozwolą uniknąć wycieków pamięci w programach C ++? Jak ustalić, kto powinien zwolnić pamięć przydzieloną dynamicznie?


26
Wydaje mi się to dość konstruktywne.
Shoerob

11
To jest konstruktywne. A odpowiedzi są poparte faktami, ekspertyzami, referencjami itp. Zobacz też liczbę pozytywnych opinii / odpowiedzi .. !!
Samitha Chathuranga

Odpowiedzi:



201

Całkowicie popieram wszystkie rady dotyczące RAII i inteligentnych wskaźników, ale chciałbym również dodać nieco wyższą wskazówkę: najłatwiejszą do zarządzania pamięcią jest pamięć, której nigdy nie przydzielono. W przeciwieństwie do języków takich jak C # i Java, w których prawie wszystko jest odniesieniem, w C ++ powinieneś umieszczać obiekty na stosie, kiedy tylko możesz. Jak zauważyło kilka osób (w tym dr Stroustrup), głównym powodem, dla którego zbieranie śmieci nigdy nie było popularne w C ++, jest to, że dobrze napisany C ++ nie produkuje zbyt wiele śmieci.

Nie pisz

Object* x = new Object;

lub nawet

shared_ptr<Object> x(new Object);

kiedy możesz po prostu pisać

Object x;

34
Chciałbym móc dać temu +10. To jest największy problem, jaki widzę obecnie u większości programistów C ++ i zakładam, że jest to spowodowane tym, że nauczyli się Javy przed C ++.
Kristopher Johnson

Bardzo interesujący punkt - Zastanawiałem się, dlaczego mam problemy z zarządzaniem pamięcią c ++ tak dużo rzadziej niż w innych językach, ale teraz widzę, dlaczego: to faktycznie pozwala rzeczy, aby przejść na stosie jak w waniliowym C
ArtOfWarfare

Więc co zrobisz, jeśli napiszesz Object x; a potem chcesz wyrzucić x? powiedzmy, że x został utworzony w głównej metodzie.
Yamcha,

3
@ user1316459 C ++ pozwala również na tworzenie zakresów w locie. Wszystko, co musisz zrobić, to zawinąć czas życia x w nawiasy klamrowe w następujący sposób: {Obiekt x; x.DoSomething; }. Po ostatnim „}” zostanie nazwany destruktor x, który zwalnia wszystkie zawarte w nim zasoby. Jeśli sam x jest pamięcią, która ma być przydzielona na stercie, sugeruję umieszczenie jej w unique_ptr, aby można było ją łatwo i odpowiednio wyczyścić.
David Peterson,

1
Robert: tak. Ross nie powiedział „Nigdy nie pisz [kodu zawierającego nowy]”, powiedział „Nie pisz [tego], kiedy możesz po prostu [położyć to na stosie]”. Duże obiekty na stercie nadal będą właściwym wywołaniem w większości sytuacji, szczególnie w przypadku kodu wymagającego dużej wydajności.
codetaku

104

Użyj RAII

  • Zapomnij o zbieraniu śmieci (zamiast tego użyj RAII). Zwróć uwagę, że nawet Garbage Collector może przeciekać (jeśli zapomnisz "zerować" niektóre odwołania w Javie / C #) i że Garbage Collector nie pomoże ci pozbyć się zasobów (jeśli masz obiekt, który uzyskał uchwyt do plik, plik nie zostanie zwolniony automatycznie, gdy obiekt wyjdzie poza zakres, jeśli nie zrobisz tego ręcznie w Javie lub użyjesz wzorca "dispose" w C #).
  • Zapomnij o zasadzie „jeden zwrot na funkcję” . Jest to dobra rada C, aby uniknąć wycieków, ale jest przestarzała w C ++ z powodu używania wyjątków (zamiast tego użyj RAII).
  • I chociaż „Sandwich Pattern” jest dobrą radą w C, jest przestarzały w C ++ z powodu używania wyjątków (zamiast tego użyj RAII).

Ten post wydaje się powtarzalny, ale w C ++ najbardziej podstawowym wzorcem, jaki należy poznać, jest RAII .

Naucz się używać inteligentnych wskaźników, zarówno z boost, TR1, jak i nawet niskiego (ale często wystarczająco wydajnego) auto_ptr (ale musisz znać jego ograniczenia).

RAII jest podstawą zarówno bezpieczeństwa wyjątków, jak i usuwania zasobów w C ++, i żaden inny wzorzec (kanapka itp.) Nie zapewni Ci obu (i przez większość czasu nie da Ci żadnego).

Zobacz poniżej porównanie kodu RAII i kodu innego niż RAII:

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

O RAII

Podsumowując (po komentarzu Ogre Psalm33 ), RAII opiera się na trzech koncepcjach:

  • Po zbudowaniu obiekt po prostu działa! Zdobądź zasoby w konstruktorze.
  • Wystarczy zniszczenie obiektu! Zwolnij zasoby w destruktorze.
  • Chodzi o lunety! Obiekty z zakresem (patrz przykład doRAIIStatic powyżej) zostaną skonstruowane w momencie ich deklaracji i zostaną zniszczone w momencie wyjścia z zakresu przez wykonanie, bez względu na sposób wyjścia (powrót, przerwa, wyjątek itp.).

Oznacza to, że w poprawnym kodzie C ++ większość obiektów nie zostanie zbudowana za pomocą newi zostanie zamiast tego zadeklarowana na stosie. A dla tych, zbudowany przy użyciu new, wszystko będzie jakoś scoped (np dołączony do inteligentnego wskaźnika).

Jako programista jest to naprawdę bardzo potężne, ponieważ nie musisz przejmować się ręczną obsługą zasobów (jak to zrobiono w C lub w przypadku niektórych obiektów w Javie, które intensywnie używają try/ finallyw tym przypadku) ...

Edytuj (2012-02-12)

„obiekty z lunetą… zostaną zniszczone… bez względu na wyjście” to nie do końca prawda. istnieją sposoby na oszukanie RAII. jakikolwiek wariant terminate () pominie czyszczenie. exit (EXIT_SUCCESS) jest pod tym względem oksymoronem.

- wilhelmtell

wilhelmtell ma co do tego całkowitą rację: istnieją wyjątkowe sposoby na oszukanie RAII, a wszystkie prowadzą do nagłego zatrzymania procesu.

Są to wyjątkowe sposoby, ponieważ kod C ++ nie jest zaśmiecony zakończeniem, zakończeniem itp. Lub w przypadku wyjątków, chcemy, aby nieobsługiwany wyjątek spowodował awarię procesu i rdzeń zrzucił obraz pamięci tak, jak jest, a nie po wyczyszczeniu.

Ale nadal musimy wiedzieć o tych przypadkach, ponieważ chociaż rzadko się zdarzają, nadal mogą się zdarzyć.

(kto wywołuje terminatelub exitw zwykłym kodzie C ++? ... Pamiętam, że musiałem radzić sobie z tym problemem podczas zabawy z GLUT : Ta biblioteka jest bardzo zorientowana na język C, posuwając się do tego, że aktywnie ją projektuje, aby utrudniać programistom C ++ takie problemy, jak brak troski o danych zaalokowanych na stosie , czy o „ciekawych” decyzjach o nigdy nie powracaniu z ich głównej pętli … Nie będę tego komentował) .


Czy klasa T nie może używać RAII, aby mieć pewność, że doRAIIStatic () nie przecieka pamięci? Na przykład T p (); p.doSandwich (); Jednak tak naprawdę niewiele o tym wiem.
Daniel O

@Ogre Psalm33: Dzięki za komentarz. Oczywiście masz rację. Dodałem oba linki do strony RAII w Wikipedii oraz krótkie podsumowanie tego, czym jest RAII.
paercebal

1
@Shiftbit: Trzy sposoby, w kolejności preferencji: _ _ _ 1. Umieść rzeczywisty obiekt w pojemniku STL. _ _ _ 2. Umieść inteligentne wskaźniki (shared_ptr) obiektów w kontenerze STL. _ _ _ 3. Umieść surowe wskaźniki wewnątrz kontenera STL, ale zawiń kontener, aby kontrolować dostęp do danych. Opakowanie upewni się, że destruktor zwolni przydzielone obiekty, a metody dostępu opakowania upewnią się, że nic nie zostanie uszkodzone podczas uzyskiwania dostępu do / modyfikowania kontenera.
paercebal

1
@Robert: W C ++ 03 użyłbyś doRAIIDynamic w funkcji, która musi nadać własność funkcji podrzędnej lub nadrzędnej (lub zasięgu globalnego). Lub gdy otrzymujesz interfejs do obiektu polimorficznego przez fabrykę (zwracając inteligentny wskaźnik, jeśli jest poprawnie napisany). W C ++ 11 jest to mniej
ważne,

2
@Robert: ... Zauważ, że zadeklarowanie obiektu na stosie nie oznacza, że ​​obiekt nie używa sterty wewnętrznie (zwróć uwagę na podwójną negację ... :-) ...). Na przykład std :: string zaimplementowany przy pomocy Small String Optimization będzie miał bufor „na stosie klasy” dla małych ciągów (~ 15 znaków) i użyje wskaźnika do pamięci w stercie dla większych ciągów ... Ale z zewnątrz, std :: string jest nadal typem wartości, który deklarujesz (zwykle) na stosie i używasz tak, jak używasz liczby całkowitej (w przeciwieństwie do: jak używałbyś interfejsu dla klasy polimorficznej).
paercebal,

25

Będziesz chciał spojrzeć na inteligentne wskazówki, takie jak inteligentne wskaźniki doładowania .

Zamiast

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost :: shared_ptr zostanie automatycznie usunięty, gdy liczba odwołań wyniesie zero:

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

Zwróć uwagę na moją ostatnią uwagę: „kiedy liczba odniesień wynosi zero, co jest najfajniejszą częścią. Jeśli więc masz wielu użytkowników swojego obiektu, nie będziesz musiał śledzić, czy obiekt jest nadal używany. Gdy nikt nie odwołuje się do Twojego obiektu wspólny wskaźnik, zostanie zniszczony.

Nie jest to jednak panaceum. Chociaż możesz uzyskać dostęp do wskaźnika podstawowego, nie chciałbyś przekazać go do interfejsu API innej firmy, chyba że jesteś pewien, co robi. Wiele razy „wysyłasz” rzeczy do innego wątku w celu wykonania pracy PO zakończeniu tworzenia zakresu. Jest to typowe z PostThreadMessage w Win32:

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

Jak zawsze, użyj czapki myślenia z dowolnym narzędziem ...



11

Większość wycieków pamięci wynika z braku jasności co do własności obiektu i czasu jego życia.

Pierwszą rzeczą do zrobienia jest alokacja na stosie, kiedy tylko możesz. Dotyczy to większości przypadków, w których trzeba przydzielić pojedynczy obiekt do jakiegoś celu.

Jeśli naprawdę potrzebujesz „nowego” obiektu, przez większość czasu będzie on miał jednego oczywistego właściciela przez resztę swojego życia. W tej sytuacji używam wielu szablonów kolekcji, które są przeznaczone do „posiadania” obiektów w nich przechowywanych za pomocą wskaźnika. Są one implementowane z kontenerami wektorów STL i map, ale mają pewne różnice:

  • Tych kolekcji nie można kopiować ani do nich przypisywać. (gdy zawierają obiekty).
  • Wstawiane są do nich wskaźniki do obiektów.
  • Kiedy kolekcja jest usuwana, destruktor jest najpierw wywoływany dla wszystkich obiektów w kolekcji. (Mam inną wersję, w której stwierdza, że ​​jest zniszczona i nie jest pusta).
  • Ponieważ przechowują wskaźniki, możesz również przechowywać odziedziczone obiekty w tych kontenerach.

Mój beaf z STL polega na tym, że jest tak skoncentrowany na obiektach wartości, podczas gdy w większości aplikacji obiekty są unikalnymi jednostkami, które nie mają znaczącej semantyki kopiowania wymaganej do użycia w tych kontenerach.


10

Aha, wy, małe dzieci i wasi nowomodni zbieracze śmieci ...

Bardzo rygorystyczne zasady dotyczące „własności” - jaki obiekt lub część oprogramowania ma prawo usunąć obiekt. Jasne komentarze i mądre nazwy zmiennych, aby było oczywiste, czy wskaźnik „posiada”, czy jest „po prostu patrz, nie dotykaj”. Aby pomóc zdecydować, kto jest właścicielem czego, postępuj zgodnie z jak największym schematem „kanapki” w każdym podprogramie lub metodzie.

create a thing
use that thing
destroy that thing

Czasami trzeba tworzyć i niszczyć w bardzo różnych miejscach; Myślę, że trudno tego uniknąć.

W każdym programie wymagającym złożonych struktur danych tworzę ścisłe, wyraźne drzewo obiektów zawierających inne obiekty - używając wskaźników „właściciela”. To drzewo modeluje podstawową hierarchię koncepcji domeny aplikacji. Na przykład scena 3D posiada obiekty, światła, tekstury. Pod koniec renderowania, gdy program kończy pracę, istnieje jasny sposób na zniszczenie wszystkiego.

Wiele innych wskaźników jest definiowanych w razie potrzeby, ilekroć jeden podmiot potrzebuje dostępu do innego, aby przeskanować przebieg lub cokolwiek; są to „tylko patrzące”. Na przykładzie sceny 3D - obiekt używa tekstury, ale jej nie posiada; inne obiekty mogą używać tej samej tekstury. Zniszczenie obiektu ma nie wywoływać zniszczenie wszelkich faktur.

Tak, to czasochłonne, ale to właśnie robię. Rzadko mam wycieki pamięci lub inne problemy. Ale potem pracuję na ograniczonej arenie wysokowydajnego oprogramowania naukowego, akwizycji danych i grafiki. Nieczęsto zajmuję się transakcjami, takimi jak w bankowości i e-commerce, graficznych interfejsów użytkownika sterowanych zdarzeniami lub asynchronicznego chaosu o dużej sieci. Może nowe sposoby mają tam przewagę!


Całkowicie się zgadzam. Pracując w środowisku osadzonym, możesz również nie mieć luksusu bibliotek innych firm.
simon

6
Nie zgadzam się. w części „użyj tej rzeczy”, jeśli zostanie wyrzucony zwrot lub wyjątek, przegapisz zwolnienie. Jeśli chodzi o wydajność, std :: auto_ptr nic by Cię nie kosztowało. Nie znaczy to, że nigdy nie koduję w taki sam sposób, jak ty. Po prostu jest różnica między 100% a 99% bezpiecznym kodem. :-)
paercebal

8

Świetne pytanie!

Jeśli używasz języka C ++ i tworzysz aplikację do obsługi procesora i pamięci w czasie rzeczywistym (np. gry), musisz napisać własnego Menedżera pamięci.

Myślę, że im lepiej możesz scalić kilka ciekawych prac różnych autorów, mogę ci podpowiedzieć:

  • Podzielnik o stałym rozmiarze jest szeroko omawiany wszędzie w sieci

  • Przydział małych obiektów został wprowadzony przez Alexandrescu w 2001 roku w jego doskonałej książce „Modern c ++ design”

  • Ogromny postęp (wraz z rozpowszechnianiem kodu źródłowego) można znaleźć w niesamowitym artykule w Game Programming Gem 7 (2008) zatytułowanym "Wysokowydajny alokator sterty" napisany przez Dimitar Lazarov

  • W tym artykule można znaleźć obszerną listę zasobów

Nie zaczynaj samodzielnie pisać bezużytecznego alokatora noob ... Najpierw DOKUMENTUJ SAM.


5

Jedną z technik, która stała się popularna w zarządzaniu pamięcią w C ++, jest RAII . Zasadniczo używasz konstruktorów / destruktorów do obsługi alokacji zasobów. Oczywiście w C ++ jest kilka innych nieprzyjemnych szczegółów związanych z bezpieczeństwem wyjątków, ale podstawowa idea jest dość prosta.

Generalnie sprawa sprowadza się do kwestii własności. Gorąco polecam przeczytanie serii Effective C ++ autorstwa Scotta Meyersa i Modern C ++ Design autorstwa Andrei Alexandrescu.



4

Używaj inteligentnych wskazówek, gdziekolwiek możesz! Całe klasy wycieków pamięci po prostu znikają.


4

Udostępniaj i poznaj zasady własności pamięci w całym projekcie. Korzystanie z reguł COM zapewnia najlepszą spójność (parametry [in] są własnością wywołującego, wywoływany musi kopiować; [out] parametry są własnością wywołującego, wywoływany musi wykonać kopię, jeśli zachowuje referencję; itp.)


4

Valgrind to również dobre narzędzie do sprawdzania przecieków pamięci programów w czasie wykonywania.

Jest dostępny dla większości wersji Linuksa (w tym Androida) i Darwina.

Jeśli używasz do pisania testów jednostkowych dla swoich programów, powinieneś nabrać nawyku systematycznego uruchamiania Valgrind na testach. Potencjalnie pozwoli to uniknąć wielu wycieków pamięci na wczesnym etapie. Zwykle łatwiej jest je również zlokalizować w prostych testach niż w pełnym oprogramowaniu.

Oczywiście ta rada obowiązuje dla każdego innego narzędzia do sprawdzania pamięci.


3

Nie używaj również ręcznie przydzielanej pamięci, jeśli istnieje klasa biblioteki standardowej (np. Wektor). Upewnij się, że jeśli naruszysz tę zasadę, masz wirtualnego destruktora.


2

Jeśli nie możesz / nie możesz użyć inteligentnego wskaźnika do czegoś (chociaż powinna to być ogromna czerwona flaga), wpisz swój kod za pomocą:

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

To oczywiste, ale upewnij się, że wpisałeś go, zanim wpiszesz kod w zakresie


2

Częstym źródłem tych błędów jest metoda, która akceptuje odniesienie lub wskaźnik do obiektu, ale pozostawia niejasną własność. Styl i konwencje komentowania mogą zmniejszyć prawdopodobieństwo wystąpienia tego problemu.

Niech przypadek, w którym funkcja przejmuje własność obiektu, będzie przypadkiem specjalnym. We wszystkich sytuacjach, w których tak się dzieje, pamiętaj o wpisaniu komentarza obok funkcji w pliku nagłówkowym, która to wskazuje. Należy dążyć do upewnienia się, że w większości przypadków moduł lub klasa, która alokuje obiekt, jest również odpowiedzialna za jego zwolnienie.

Używanie const może w niektórych przypadkach bardzo pomóc. Jeśli funkcja nie modyfikuje obiektu i nie przechowuje odniesienia do niego, które utrzymuje się po zwróceniu, zaakceptuj odwołanie do stałej. Czytając kod wywołującego będzie oczywiste, że Twoja funkcja nie przyjęła własności obiektu. Mogłeś mieć tę samą funkcję, która akceptowała wskaźnik inny niż stały, a wywołujący mógł założyć lub nie, że wywoływany zaakceptował własność, ale przy odwołaniu do stałej nie ma wątpliwości.

Nie używaj odwołań innych niż stałe w listach argumentów. Czytając kod dzwoniącego, jest bardzo niejasne, że wywoływany mógł zachować odniesienie do parametru.

Nie zgadzam się z komentarzami zalecającymi liczone wskaźniki referencyjne. Zwykle działa to dobrze, ale gdy masz błąd i nie działa, zwłaszcza jeśli twój destruktor robi coś nietrywialnego, na przykład w programie wielowątkowym. Zdecydowanie spróbuj dostosować swój projekt, aby nie potrzebować liczenia referencji, jeśli nie jest to zbyt trudne.


2

Wskazówki w kolejności ważności:

-Tip # 1 Zawsze pamiętaj, aby zadeklarować swoje destruktory jako „wirtualne”.

-Tip # 2 Użyj RAII

-Tip # 3 Użyj inteligentnych wskaźników doładowania

-Tip # 4 Nie pisz swoich własnych błędnych Smartpointerów, używaj boost (w projekcie, w którym teraz jestem, nie mogę użyć boostu i cierpiałem na konieczność debugowania własnych inteligentnych wskaźników, na pewno bym nie wziął znowu ta sama trasa, ale w tej chwili nie mogę dodać wzmocnienia do naszych zależności)

-Porada nr 5 Jeśli jest to przypadkowe / niekrytyczne dla wydajności (jak w grach z tysiącami obiektów), spójrz na kontener wskaźnika doładowania Thorstena Ottosena

-Wskazówka 6 Znajdź nagłówek wykrywania wycieków dla wybranej platformy, taki jak nagłówek „vld” Visual Leak Detection


Może brakuje mi sztuczki, ale jak słowa „gra” i „brak krytycznego znaczenia dla wydajności” mogą znajdować się w tym samym zdaniu?
Adam Naylor

Gry są oczywiście przykładem scenariusza krytycznego. Mogło się nie udać
Robert Gould

Wskazówka nr 1 powinna być stosowana tylko wtedy, gdy klasa ma przynajmniej jedną metodę wirtualną. Nigdy nie narzuciłbym bezużytecznego wirtualnego destruktora klasy, która nie ma służyć jako klasa bazowa w polimorficznym drzewie dziedziczenia.
antred

1

Jeśli możesz, użyj boost shared_ptr i standardowego C ++ auto_ptr. Te przekazują semantykę własności.

Kiedy zwracasz auto_ptr, mówisz dzwoniącemu, że dajesz mu prawo własności do pamięci.

Kiedy zwracasz shared_ptr, mówisz dzwoniącemu, że masz do niego odniesienie i że przejmuje on część własności, ale nie jest to wyłącznie jego odpowiedzialność.

Ta semantyka dotyczy również parametrów. Jeśli dzwoniący przekazuje Ci auto_ptr, przekazuje Ci własność.


1

Inni wspominali przede wszystkim o sposobach unikania wycieków pamięci (jak inteligentne wskaźniki). Jednak narzędzie do profilowania i analizy pamięci jest często jedynym sposobem na wyśledzenie problemów z pamięcią, gdy już się pojawią.

Valgrind memcheck to doskonały darmowy.


1

Tylko w przypadku MSVC dodaj następujący tekst na początku każdego pliku .cpp:

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

Następnie, podczas debugowania w VS2003 lub nowszym, zostaniesz poinformowany o wszelkich wyciekach, gdy twój program zostanie zamknięty (śledzi nowe / usunięte). To podstawowe, ale pomogło mi w przeszłości.


1

valgrind (dostępny tylko dla platform * nix) jest bardzo dobrym narzędziem do sprawdzania pamięci


1

Jeśli zamierzasz zarządzać pamięcią ręcznie, masz dwa przypadki:

  1. Utworzyłem obiekt (być może pośrednio, wywołując funkcję, która przydziela nowy obiekt), używam go (lub używa go funkcja, którą wywołuję), a następnie zwalniam.
  2. Ktoś dał mi odniesienie, więc nie powinienem go zwalniać.

Jeśli chcesz złamać którąkolwiek z tych zasad, udokumentuj to.

Chodzi o własność wskaźnika.


1
  • Staraj się unikać dynamicznego przydzielania obiektów. Dopóki klasy mają odpowiednie konstruktory i destruktory, używaj zmiennej typu klasy, a nie wskaźnika do niej, i unikasz dynamicznej alokacji i zwalniania alokacji, ponieważ kompilator zrobi to za Ciebie.
    Właściwie jest to również mechanizm używany przez "inteligentne wskaźniki", określany jako RAII przez niektórych innych autorów ;-).
  • Podczas przekazywania obiektów do innych funkcji preferuj parametry odniesienia nad wskaźniki. Pozwala to uniknąć niektórych możliwych błędów.
  • Tam, gdzie to możliwe, zadeklaruj parametry const, zwłaszcza wskaźniki do obiektów. W ten sposób obiekty nie mogą zostać zwolnione „przypadkowo” (chyba że wyrzucisz const ;-))).
  • Zminimalizuj liczbę miejsc w programie, w których dokonujesz alokacji i zwalniania pamięci. E. g. jeśli kilkakrotnie przydzielisz lub zwolnisz ten sam typ, napisz dla niego funkcję (lub metodę fabryczną ;-)).
    W ten sposób możesz łatwo tworzyć dane wyjściowe debugowania (które adresy są przydzielane i zwalniane, ...) w razie potrzeby.
  • Użyj funkcji fabryki, aby przydzielić obiekty kilku pokrewnych klas z jednej funkcji.
  • Jeśli twoje klasy mają wspólną klasę bazową z wirtualnym destruktorem, możesz zwolnić je wszystkie przy użyciu tej samej funkcji (lub metody statycznej).
  • Sprawdź swój program za pomocą narzędzi takich jak purify (niestety wiele $ / € / ...).

0

Możesz przechwycić funkcje alokacji pamięci i sprawdzić, czy są jakieś strefy pamięci, które nie są zwalniane po zakończeniu programu (chociaż nie jest to odpowiednie dla wszystkich aplikacji).

Można to również zrobić w czasie kompilacji, zastępując operatorów new i delete oraz inne funkcje alokacji pamięci.

Na przykład sprawdź w tej witrynie [Debugowanie alokacji pamięci w C ++] Uwaga: Istnieje sztuczka z operatorem usuwania również coś takiego:

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

Możesz przechowywać w niektórych zmiennych nazwę pliku i kiedy przeciążony operator usuwania będzie wiedział, z którego miejsca został wywołany. W ten sposób możesz mieć ślad każdego usunięcia i malloc ze swojego programu. Pod koniec sekwencji sprawdzania pamięci powinieneś być w stanie zgłosić, który przydzielony blok pamięci nie został „usunięty”, identyfikując go za pomocą nazwy pliku i numeru linii, co jest chyba tym, czego chcesz.

Możesz także wypróbować coś takiego jak BoundsChecker w programie Visual Studio, które jest dość interesujące i łatwe w użyciu.


0

Otaczamy wszystkie nasze funkcje alokacji warstwą, do której dołączamy krótki ciąg z przodu i flagę wartowniczą na końcu. Na przykład miałbyś wywołanie "myalloc (pszSomeString, iSize, iAlignment); lub new (" description ", iSize) MyObject (); które wewnętrznie przydziela określony rozmiar plus wystarczającą ilość miejsca na nagłówek i wartownika. Oczywiście , nie zapomnij skomentować tego w przypadku kompilacji bez debugowania! To zajmuje trochę więcej pamięci, ale korzyści znacznie przewyższają koszty.

Ma to trzy zalety - po pierwsze, pozwala łatwo i szybko śledzić, który kod przecieka, wykonując szybkie wyszukiwanie kodu przydzielonego w określonych „strefach”, ale nie czyszczonego, gdy te strefy powinny się zwolnić. Przydatne może być również wykrycie, kiedy granica została nadpisana, sprawdzając, czy wszystkie wartowniki są nienaruszone. Uratowało nas to wiele razy, gdy próbowaliśmy znaleźć te dobrze ukryte awarie lub błędy w tablicy. Trzecią korzyścią jest śledzenie wykorzystania pamięci, aby zobaczyć, kim są najwięksi gracze - na przykład zestawienie niektórych opisów w MemDump informuje, kiedy „dźwięk” zajmuje o wiele więcej miejsca, niż się spodziewałeś.


0

C ++ jest zaprojektowany z myślą o RAII. Myślę, że nie ma lepszego sposobu na zarządzanie pamięcią w C ++. Uważaj jednak, aby nie przydzielać bardzo dużych porcji (takich jak obiekty bufora) w zakresie lokalnym. Może to spowodować przepełnienie stosu, a jeśli jest błąd w sprawdzaniu granic podczas korzystania z tego fragmentu, możesz nadpisać inne zmienne lub zwrócić adresy, co prowadzi do wszelkiego rodzaju luk w zabezpieczeniach.


0

Jednym z jedynych przykładów przydzielania i niszczenia w różnych miejscach jest tworzenie wątków (przekazywany parametr). Ale nawet w tym przypadku jest to łatwe. Oto funkcja / metoda tworzenia wątku:

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

Tutaj zamiast funkcji wątku

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

Dość łatwo, prawda? W przypadku niepowodzenia tworzenia wątku zasób zostanie zwolniony (usunięty) przez auto_ptr, w przeciwnym razie własność zostanie przekazana do wątku. Co jeśli wątek jest tak szybki, że po utworzeniu zwalnia zasób przed rozszerzeniem

param.release();

jest wywoływany w głównej funkcji / metodzie? Nic! Ponieważ „powiemy” auto_ptr, aby zignorował cofnięcie alokacji. Czy zarządzanie pamięcią w C ++ jest łatwe, prawda? Twoje zdrowie,

Ema!


0

Zarządzaj pamięcią w taki sam sposób, jak zarządzasz innymi zasobami (uchwytami, plikami, połączeniami bazy danych, gniazdami ...). GC też ci nie pomoże.


-3

Dokładnie jeden zwrot z dowolnej funkcji. W ten sposób możesz tam dokonać zwolnienia i nigdy tego nie przegapić.

W przeciwnym razie zbyt łatwo popełnić błąd:

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.

Twoja odpowiedź nie pasuje do przykładowego kodu tutaj? Zgadzam się z odpowiedzią „tylko jeden powrót”, ale przykładowy kod pokazuje, czego NIE robić.
simon

1
Celem C ++ RAII jest właśnie uniknięcie tego rodzaju kodu, który napisałeś. W C prawdopodobnie jest to właściwe postępowanie. Ale w C ++ twój kod jest wadliwy. Na przykład: A co jeśli new b () rzuci? Wyciekasz.
paercebal
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.