2009-08-20 7 views
6

Uruchamianie valgrind lub oczyszczania będzie następnymi krokami Ale podczas pisania samego kodu, jak upewnić się, że nie spowoduje to wycieków pamięci? można zapewnić następujące rzeczy: - 1: liczba nowych równa usuwać 2: otwarty deskryptor pliku jest zamknięty lub nieJak zapewnić, że podczas pisania samego kodu C++ nie spowoduje to wycieków pamięci?

Czy istnieje coś innego?

+1

ostrożnie z nowym == usunąć pomysł. Załóżmy, że masz kilka miejsc, w których pamięć może zostać zwolniona, każda z kontrolą zerową, tzn. 'If (p) delete p;' Możesz skończyć z nowym == N * usuń gdzie N jest arbitralne. – ezpz

+3

'delete 0' jest no-op, więc nie powinieneś nigdy sprawdzać, czy wskaźnik jest pusty przed usunięciem. –

+0

Dziękuję Peter! To jest mój pee numer 1! "if (p) delete p;" należy ponownie napisać jako: "delete p; p = 0;" lub coś podobnego. – Thomi

Odpowiedz

25

Użyj RAII idiom wszędzie można

Stosować inteligentne wskaźniki, np std :: auto_ptr w razie potrzeby. (Nie używać auto_prt którymś ze standardowych zbiorach, gdyż nie będzie działać, jak myślisz, że będzie)

+1

+1 ode mnie. Ale dlaczego "wszędzie można", a nie tylko zawsze (biorąc pod uwagę inteligentne wskaźniki forma RAII)? –

+2

@orsogufo, Nie lubię zdań absolutnych. Gdybym powiedział "zawsze", ktoś mógłby wymyślić sytuację, w której RAII nie jest odpowiednie (tylko dlatego, że nie myślałem o takiej sytuacji, nie oznacza to, że ktoś inny tego nie zrobił). – Glen

+0

Tak. jeśli używasz nowych i usuń bez inteligentnych wskaźników, to robisz coś nie tak. – Thomi

7
+0

dobra pierwsza rada, ale kończy się niepowodzeniem w kręgach – gimpf

+2

Tak, druga rada powinna brzmieć "Bądź inteligentny" :) – Aamir

+2

Użyj boost :: weak_ptr <>, aby rozwiązać cykle na wykresie referencyjnym. – janm

0

Upewnij pamięci współdzielonej utworzony przez aplikację jest uwolniony, jeśli nikt nie używa go już, czyste pliki odwzorowane w pamięci ...

Zasadniczo upewnij się, że czyścisz każdy rodzaj zasobu, który aplikacja bezpośrednio lub pośrednio tworzy. Deskryptory plików to tylko jeden typ zasobu, z którego aplikacja może korzystać w czasie wykonywania.

1

Zawsze używam std::auto_ptr, kiedy potrzebuję utworzyć nowy obiekt na stercie.

std::auto_ptr<Foo> CreateFoo() 
{ 
    return std::auto_ptr<Foo>(new Foo()); 
} 

Nawet jeśli zadzwonisz

CreateFoo() 

nie będzie przeciekać

+0

Poza tym nie można skopiować pliku std :: auto_ptr. Oznacza to, że nie można go umieścić w standardowych pojemnikach, między innymi. –

+0

Można go oczywiście skopiować, inaczej nie będzie można pisać funkcji, które zwracają auto_ptrs. –

0

jeśli popełnisz żadnego drzewa lub wykresu rekurencyjnie w kodzie dla struktury danych może jeść wszystko z pamięci.

14

Unikaj dynamicznego tworzenia obiektów, jeśli to możliwe. Programiści Java i pochodzące z innych podobnych języków często pisać rzeczy jak:

string * s = new string("hello world"); 

gdy powinny one napisane:

string s = "hello world"; 

Podobnie, tworzą zbiory wskaźników, podczas gdy powinny tworzyć zbiory wartości. Na przykład, jeśli masz klasę tak:

class Person { 
    public: 
     Person(const string & name) : mName(name) {} 
     ... 
    private: 
     string mName; 
}; 

Zamiast pisania kodu, takich jak:

vector <Person *> vp; 

lub nawet:

vector <shared_ptr <Person> > vp; 

zamiast używać wartości:

vector <Person> vp; 

Możesz łatwo dodać do takiego av ector:

vp.push_back(Person("neil butterworth")); 

i cała pamięć dla osoby i wektora jest zarządzana dla Ciebie.Oczywiście, jeśli potrzebujesz kolekcji typów polimorficznych, powinieneś użyć (inteligentnych) wskaźników:

+2

Dzięki temu jest praktycznie niemożliwe wykorzystanie polimorfizmu, utrudnia przekazywanie danych i może przyczyniać się do wzdęć. Nie stosuj tej porady hurtowo, nie myśląc o tym. –

+1

Czy przeczytałeś ostatnie zdanie z mojej odpowiedzi? –

+1

Albo pierwszy, przyjdź do tego - powiedziałem "gdzie to możliwe". –

0

Dostępne są narzędzia do statycznej analizy kodu, które robią tego typu rzeczy; wikipedia to dobre miejsce do rozpoczęcia wyszukiwania. Zasadniczo, poza byciem ostrożnym i wyborem właściwych pojemników, nie można zagwarantować, że kod napiszesz - stąd potrzeba narzędzi takich jak valgrind i gdb.

4

Minimalizuj wywołania do nowych za pomocą kontenerów STL do przechowywania danych.

1

Podstawowe kroki są dwojakie:

Po pierwsze, należy pamiętać, że każda nowa wymaga usunięcia. Tak więc, kiedy używasz nowego operatora, zwiększ swoją świadomość tego, co ten obiekt będzie robić, w jaki sposób będzie on używany i jak będzie zarządzany jego czas życia.

Po drugie, upewnij się, że nigdy nie zastępujesz wskaźnika. Możesz to zrobić za pomocą klasy inteligentnego wskaźnika zamiast surowych wskaźników, ale jeśli będziesz absolutnie pewien, że nigdy nie użyjesz go z niejawną konwersją. (przykład: przy użyciu biblioteki MSXML, utworzyłem inteligentny wskaźnik CCOMPtr do przechowywania węzłów, aby uzyskać węzeł, który wywołuje się metodą get_Node, przekazując adres inteligentnego wskaźnika - który miał operator konwersji, który zwrócił typ wskaźnika bazowego.) oznaczało to, że jeśli inteligentny wskaźnik już posiadał dane, dane tego członka zostałyby zastąpione, przeciekając poprzedni węzeł).

Myślę, że te 2 przypadki są czasami, kiedy możesz przeciekać pamięć. Jeśli używasz tylko inteligentnego wskaźnika bezpośrednio - nigdy nie pozwalając na ujawnienie swoich wewnętrznych danych, jesteś bezpieczny od tego ostatniego problemu. Jeśli zawiniesz cały kod, który używa nowego i usuniesz w klasie (np. Przy użyciu RAII), jesteś całkiem bezpieczny od tego pierwszego.

Unikanie wycieków pamięci w C++ jest bardzo proste, jeśli wykonasz powyższe czynności.

5
  1. Zastosowanie RAII
  2. Ukryj domyślne kopiowania ctors, operator =() w każdej klasie, chyba) klasa jest trywialne i tylko wykorzystuje natywne typy i wiesz to zawsze będzie tak b) jawnie zdefiniować własną

na 1) RAII, chodzi o to, aby mieć Usuwa się automatycznie, jeśli znajdziesz się myślenie „Po prostu nazywa nowy, muszę pamiętać, żeby zadzwonić usuwać gdzieś” wtedy "Robię coś źle. Usunięcie powinno być: a) automatyczne lub b) zostać wprowadzone do dtor (i które dtor powinno być oczywiste).

2) Ukrywanie wartości domyślnych. Identyfikowanie nieuczciwych domyślnych criesów itp. Może być koszmarem, najłatwiej jest ich uniknąć, ukrywając je. Jeśli masz ogólny obiekt "root", z którego wszystko pochodzi (może być przydatny do debugowania/profilowania), ukryj tutaj wartości domyślne, a kiedy coś spróbuje przypisać/skopiować klasę odziedziczającą, kompilator będzie barfował, ponieważ etc etc dostępne w klasie bazowej.

+2

boost :: noncopyable jest bardzo przydatny dla # 2 – jalf

+0

@jalf: Dzięki, rozwiążemy problem, jeśli będę musiał ponownie zrobić poważne C++. Nie używałem C++ w gniewie od 5 lat lub więcej teraz :) –

+0

Wznowiono, ale dlaczego ukrywam operator *()? –

1

Dwa proste reguły kciuka:

  • Nigdy nazywają delete jednoznacznie (poza klasą RAII, że jest). Każda alokacja pamięci powinna należeć do klasy RAII, która wywołuje usuwanie w destruktorze.
  • Niemal jawnie nie wywoływać new.Jeśli tak, powinieneś natychmiast opakować wynikowy wskaźnik w inteligentny wskaźnik, który przejmuje własność alokacji i działa jak wyżej.

W własnych klas RAII dwa występujące problemy to:

  • Brak obsługi kopiowania poprawnie: Kto przejmie na własność pamięci, jeśli obiekt jest kopiowany? Czy tworzą nową alokację? Czy implementujesz zarówno konstruktora kopiowania, jak i operatora przypisania? Czy ten ostatni obsługuje samodzielne zadanie?
  • Nie uwzględnia bezpieczeństwa wyjątków. Co się stanie, jeśli wyjątek zostanie zgłoszony podczas operacji (na przykład przydziału)? Czy obiekt powraca do stanu zgodnego? (powinien to robić zawsze: , zawsze, bez względu na wszystko) Czy przywraca stan sprzed operacji? (powinno to zrobić, gdy to możliwe) std::vector musi sobie z tym poradzić, na przykład push_back. Mogłoby to spowodować zmianę rozmiaru wektora, co oznacza 1) alokację pamięci, która może zostać rzucona, oraz 2) wszystkie istniejące elementy muszą zostać skopiowane, z których każdy może rzutować. Algorytm taki jak std::sort musi sobie z tym poradzić. Musi zadzwonić do porównywalnika dostarczonego przez użytkownika, który mógłby również wyrzucić! jeśli tak się stanie, czy sekwencja pozostała w ważnym stanie? Czy tymczasowe obiekty są niszczone w czysty sposób?

Jeśli zajmiesz się powyższymi dwiema sprawami w swoich klasach RAII, ich przeciekanie jest prawie niemożliwe. Jeśli używasz klas RAII do zawijania wszystkich alokacji zasobów (alokacji pamięci, uchwytów plików, połączeń z bazami danych i każdego innego typu zasobu, który ma zostać pobrany i wydany), aplikacja nie może przeciekać pamięci.

+0

Skorzystaj ze standardu i zwiększ inteligentne wskaźniki podczas budowania tych klas. Zaoszczędzi ci to pracy i subtelnych błędów. –

+1

Oczywiście. Gdy dostępne są standardowe narzędzia do robienia tego, czego potrzebujesz, * użyj ich *. Ale nadal twierdzę, że ważne jest, aby zdawać sobie sprawę z tych subtelnych problemów w pierwszej kolejności. – jalf

3

Jestem z Glenem i Jalf w związku z RAII przy każdej okazji.

IMHO powinieneś dążyć do napisania całkowicie bezbłędnego kodu. Jedynymi wyraźnymi "usuwaniami" powinny być implementacje klas inteligentnych wskaźników. Jeśli chcesz napisać "usuń", znajdź zamiast tego odpowiedni typ inteligentnego wskaźnika. Jeśli żaden z "standardowych w branży" (boost itp.) Nie pasuje, a ty chcesz napisać coś nowego, istnieje szansa, że ​​twoja architektura zostanie uszkodzona lub przynajmniej wystąpią trudności w utrzymaniu w przyszłości.

Od dawna uważam, że wyraźne "usuwanie" jest dla zarządzania pamięcią, co "goto" ma kontrolować przepływ. Więcej na ten temat w this answer.

+0

Powiedziałbym to samo za nowe. W przypadku wielu zasobów wolę pisać pełną klasę RAII, która tworzy zasób i udostępnia go. Inteligentny wskaźnik jest odpowiedzialny tylko za usuwanie, więc wymaga, aby zasób został utworzony gdzie indziej, co jest nieco podatne na błędy. – jalf

+0

Interesujące. Mam już dobre wyniki z "usuwania czystek" na starym kodzie, ale nigdy nie rozważałem rozszerzenia tego na nowy, prawdopodobnie dlatego, że początkowo przychodziłem do niego z punktu widzenia uczynienia kodu C++ bardziej "podobnym do Java" . Podejrzane szablony variadic C++ 0x byłyby użyteczne gdzieś do ułatwienia przekazywania argumentów konstruktora do typów inteligentnych wskaźników, które również byłyby odpowiedzialne za wywoływanie nowych. – timday

+1

Istnieje mechanizm boost :: make_shared w boost, który tworzy boost :: shared_ptr dla ciebie, przekazując przekazane argumenty do konstruktora typu: 'boost :: shared_ptr ptr = make_shared (Arg1, Arg2, ... " –

0

Włącz urządzenie valgrind i testowanie systemu na wczesnym etapie cyklu rozwoju i używaj go konsekwentnie.

+1

O wiele lepiej nie mieć problemów z zarządzaniem pamięcią. –

Powiązane problemy