W C ++ ile czasu programista spędza na zarządzaniu pamięcią


39

Ludzie, którzy są przyzwyczajeni do bezużytecznych języków, często boją się zarządzania pamięcią przez C ++. Istnieją narzędzia, takie jak auto_ptri, shared_ptrktóre obsłużą wiele zadań zarządzania pamięcią. Wiele bibliotek C ++ poprzedza te narzędzia i ma swój własny sposób obsługi zadań zarządzania pamięcią.

Ile czasu spędzasz na zadaniach związanych z zarządzaniem pamięcią?

Podejrzewam, że jest to w dużym stopniu zależne od zestawu bibliotek, których używasz, więc powiedz, do których z nich odnosi się twoja odpowiedź, a jeśli poprawią lub pogorszą.


1
Nie za bardzo, naprawdę ... Zwłaszcza z C ++ 0x, referencjami i STL. Możesz nawet pisać kod bez zarządzania pamięcią.
Koder

9
Ogólnie: Nie tak bardzo, jeśli masz doświadczenie. Dużo, jeśli jesteś nowicjuszem w C ++ (-> zwykle poluje na wycieki pamięci / zasobów).
MAR,

1
Znajduję teraz prawdziwe pytanie, bardziej o ściganie nieaktualnych referencji. I za każdym razem jest to dość oczywiste, po prostu denerwujące, że nie został wcześniej złapany: p
Matthieu M.

Wiem, że to stare, ale zarządzanie pamięcią IMO jest integralną częścią bycia dobrym programistą. Abstrakcje takie jak kontenery STL są ładne, ale nieznajomość pamięci jest sprzeczna z samą ideą obliczeń. Równie dobrze można zapytać, w jaki sposób można wyeliminować manipulację algebraiczną, logikę i zapętlanie z arsenału programisty.
imallett

Co powiesz na „ile czasu zajmuje debugowanie zarządzania pamięcią, które poszło nie tak?” Samo zarządzanie pamięcią jest możliwe i nie tak trudne w C ++. Faktem jest, że konfiguracja jest precyzyjnym rzemiosłem i jest bardzo podatna na tworzenie kopii zapasowych. Kiedy się spieprzysz, możesz nawet nie zauważyć, a powrót do starych błędów z nieobliczalnymi zachowaniami, które narastały w czasie, jest prawdziwym ujściem w czasie rzeczywistym, którego powinieneś się bać. Dlatego współczesne języki, które nie są śmieciami (mam na myśli rdzę), przeniosły dużą odpowiedzialność za sprawdzenie typowych błędów na kompilator.
ZJR,

Odpowiedzi:


54

Współczesne C ++ sprawia, że ​​nie musisz się martwić o zarządzanie pamięcią, dopóki nie będziesz musiał, tj. Dopóki nie będziesz musiał ręcznie organizować pamięci, głównie w celu optymalizacji, lub jeśli kontekst zmusi cię do tego (pomyśl o dużych ograniczeniach). Pisałem całe gry bez manipulowania surową pamięcią, martwiłem się tylko o używanie pojemników, które są odpowiednim narzędziem do pracy, jak w każdym języku.

To zależy od projektu, ale przez większość czasu nie chodzi o zarządzanie pamięcią, ale o czas życia obiektu. Zostało to rozwiązane za pomocą inteligentnych wskaźników , czyli jednego z idiomatycznych narzędzi C ++ wynikających z RAII .

Po zrozumieniu RAII zarządzanie pamięcią nie będzie stanowiło problemu.

Następnie, gdy będziesz musiał uzyskać dostęp do surowej pamięci, zrobisz to w bardzo specyficznym, zlokalizowanym i możliwym do zidentyfikowania kodzie, tak jak w implementacjach obiektów puli, a nie „wszędzie”.

Poza tego rodzaju kodem nie będziesz musiał manipulować pamięcią, a jedynie trwałość obiektów.

„Trudną” częścią jest zrozumienie RAII.


10
Absolutnie prawdziwe. W ciągu ostatnich 5 lat pisałem „usuń” tylko podczas pracy ze starszym kodem.
drxzcl

3
Pracuję w środowisku osadzonym o dużym rozmiarze stosu. Tak fajne jak RAII, nie działa dobrze, jeśli przestrzeń na stosie jest na wagę złota. Wróćmy więc do mikrozarządzania wskaźnikami.
bastibe

1
@nikie Korzystam z bibliotek inteligentnych wskaźników w kodzie, które manipulują ich API, następnie używam standardowych lub wzmacniających inteligentnych wskaźników w kodzie specyficznym dla mojej aplikacji (jeśli to ja decyduję o tym). Jeśli możesz wyizolować kod biblioteki w niektórych modułach, które pokazują, jak są one używane w twojej aplikacji, unikniesz zanieczyszczenia interfejsu API z zależności.
Klaim

12
@Paperflyer: RAII nie zajmie więcej miejsca na stosie niż deleteręcznie, chyba że masz jedną gównianą implementację.
DeadMG,

2
@Paperflyer: Inteligentny wskaźnik na stercie zajmuje to samo miejsce; różnica polega na tym, że kompilator wstawia kod dezalokacji zasobów przy wszystkich wyjściach z funkcji. A ponieważ jest to tak szeroko stosowane, jest to zwykle dobrze zoptymalizowane (np. Składanie wielu wyjść razem w sposób, którego nie można - nie można wstawić kodu po a return)
MSalters

32

Zarządzanie pamięcią służy do odstraszania dzieci, ale jest to tylko jeden rodzaj zasobów, którymi musi się zająć programista. Pomyśl o uchwytach plików, połączeniach sieciowych i innych zasobach uzyskanych z systemu operacyjnego.

Języki, które obsługują wyrzucanie elementów bezużytecznych, zwykle nie tylko ignorują istnienie tych zasobów, ale także utrudniają ich prawidłowe obsługiwanie, ponieważ nie zapewniają destruktora.

Krótko mówiąc, sugerowałbym, aby nie poświęcić dużo czasu programistom C ++ na martwienie się o zarządzanie pamięcią. Jak wskazuje odpowiedź klaim , kiedy już opanujesz RAII, reszta to tylko refleks.


3
Szczególnie podoba mi się sposób, w jaki HttpWebRequest.GetResponse przecieka uchwyty i zaczyna zawieszać się w językach GC. GC jest spoko, dopóki nie zacznie ssać, ponieważ zasoby wciąż przeciekają. msdn.microsoft.com/en-us/library/... Patrz „Uwaga”.
Koder

6
+1 za wyświetlanie pamięci jako zasobu. Stary kod czy nie, ile razy musimy głośno krzyczeć: Zarządzanie pamięcią to umiejętność, a nie przekleństwo .
aquaherd

4
@ Koder Nie jestem pewien, czy podążę ... GC jest do bani, bo i tak można nadużywać zasobów ..? Myślę, że C # wykonuje dobrą robotę, zapewniając deterministyczne uwalnianie zasobów za pomocą IDisposable ...
Max

8
@Max: Ponieważ jeśli zostaną zebrane śmieci, nie oczekuję, że będę się martwić o głupie zasoby za pomocą niestandardowych IDisposables. Zasoby opuściły zakres, to wszystko, należy je wyczyścić. W rzeczywistości jednak wciąż muszę myśleć i zgadywać, które wyciekną, a które nie. W pierwszej kolejności pokonuje wszelkie powody, by używać języka GC.
Koder

5
@deadalnix Mają finalizekonstrukcję. Nie wiesz jednak, kiedy zostanie on wywołany. Czy to nastąpi zanim zabraknie gniazd lub obiektów WebResponse? Znajdziesz mnóstwo artykułów, które mówią ci, że nie powinieneś polegać finalize- z uzasadnionego powodu.
Dysaster

13

Prawie żaden. Nawet stare technologie, takie jak COM, możesz pisać niestandardowe programy usuwające standardowe wskaźniki, które przekonwertują je w bardzo krótkim czasie. Na przykład std::unique_ptrmożna przekonwertować, aby unikatowo przechowywać odwołanie COM z pięcioma liniami niestandardowego usuwacza. Nawet jeśli musisz ręcznie napisać własną procedurę obsługi zasobów, rozpowszechnienie wiedzy takiej jak SRP i kopiowanie i zamiana sprawia, że ​​stosunkowo łatwo jest napisać klasę zarządzającą zasobami, aby używać jej na zawsze.

Rzeczywistość jest taka, że ​​wspólny, unikalny i nieposiadający własności wszystkie są dostarczane z kompilatorem C ++ 11, a ty po prostu musisz napisać małe adaptery, aby działały nawet ze starym kodem.


1
Ile musisz umieć posługiwać się C ++, aby: a) napisać niestandardowy usuwacz b) wiedzieć, że potrzebujesz niestandardowego usuwacza? Pytam, ponieważ wydaje się, że łatwo jest wybrać nowy język GC i zbliżyć się do poprawienia, nie wiedząc o wszystkim - czy łatwo jest również dobrze przejść w C ++?
Sean McMillan

1
@SeanMcMillan: Niestandardowe programy do usuwania i pisania są łatwe w pisaniu i wdrażaniu, wspomniany model COM to pięć wierszy dla wszystkich typów modeli COM i każdy, kto ma podstawowe szkolenie z nowoczesnego C ++, powinien się z nimi zapoznać. Nie możesz wybrać języka GCed, ponieważ niespodzianka - GC nie zbiera obiektów COM. Lub uchwyty do plików. Lub pamięć uzyskana z innych systemów. Lub połączenia z bazą danych. RAII zrobi te wszystkie rzeczy.
DeadMG

2
Przez „Pick up a GC'd language” miałem na myśli, że przeskoczyłem między Javą / C # / Ruby / Perl / JavaScript / Python i wszystkie mają ten sam styl zarządzania zasobami - pamięć jest w większości automatyczna i wszystko inne , musisz zarządzać. Wydaje mi się, że mówisz, że narzędzia do zarządzania C ++ pozwalają zarządzać uchwytami plików / połączeniami db / etc w taki sam sposób jak pamięć i że jest to stosunkowo proste, gdy się go nauczysz. Nie operacja mózgu. Czy dobrze rozumiem?
Sean McMillan

3
@SeanMcMillan: Tak, dokładnie tak i nie jest to skomplikowane.
DeadMG

11

Kiedy byłem programistą C ++ (dawno temu), dużo czasu martwiłem się błędem zarządzania pamięcią, próbując naprawić trudne do odtworzenia błędy .

W przypadku modemu C ++ zarządzanie pamięcią jest o wiele mniejszym problemem, ale możesz zaufać wszystkim w dużym zespole, jeśli chodzi o prawidłowe działanie. Jaki jest koszt / czas:

  • Szkolenie (niewielu programistów przyjeżdża z dobrym zrozumieniem problemów)
  • Recenzje kodu, aby znaleźć problemy z zarządzaniem pamięcią
  • Debugowanie problemów z zarządzaniem pamięcią
  • Zawsze należy pamiętać, że błąd w jednej części aplikacji może wynikać z problemu zarządzania pamięcią w niezwiązanej części aplikacji .

Więc nie chodzi tylko o czas spędzony na „ robieniu ”, to raczej problem w dużych projektach.


2
Wydaje mi się, że niektóre projekty w C ++ bardzo chciały naprawić niektóre wycieki pamięci z powodu źle napisanego kodu. Zły kod się wydarzy, a kiedy to zrobi, może również zająć dużo czasu innym ludziom.
Jeremy

@Jeremy, odkryłem, że kiedy przeniosłem się z C ++ do C #, nadal było tak źle źle napisany kod (jeśli nie więcej), ale przynajmniej bardzo łatwo było znaleźć część programu, która zawierała dany błąd.
Ian

1
tak, właśnie dlatego większość sklepów przeniosła się na Javę lub .NET. Czyszczenie pamięci zmniejsza nieuchronne uszkodzenie złego kodu.
Jeremy

1
Co dziwne, nie mamy tych problemów.
David Thornley,

1
@DavidThornley, myślę, że wiele problemów polegało na pisaniu kodu interfejsu użytkownika w C ++, obecnie większość kodu C ++, który widzę, nie jest interfejsem użytkownika
Ian

2

Często używam bibliotek boost i TR1, dzięki czemu zarządzanie pamięcią w ścisłym tego słowa znaczeniu (nowe / usuwanie) nie jest problemem. Z drugiej strony przydział pamięci w C ++ nie jest tani i należy zwrócić uwagę na to, gdzie tworzone są te fantazyjne wspólne wskaźniki. W końcu często używasz obszarów roboczych lub pracujesz z pamięcią stosu. Ogólnie rzecz biorąc, powiedziałbym, że jest to głównie problem projektowy, a nie problem implementacyjny.


2

ile czasu zajmuje klientowi? bardzo mało, kiedy już to zrozumiesz. kiedy kontener zarządza czasem życia i referencjami, jest to naprawdę bardzo łatwe. imo, jest to o wiele prostsze niż ręczne liczenie referencji i jest praktycznie przezroczyste, jeśli weźmiesz pod uwagę kontener, którego używasz jako dokumentację, której kompilator w wygodny sposób zapobiega przeprowadzaniu nieważnych przeniesień własności w dobrze zaprojektowanym, bezpiecznym systemie.

większość czasu, który spędzam (jako klient), zawiera typy z innych aplikacji, więc działają one dobrze w kontekście twoich programów. Przykład: to jest mój ThirdPartyFont pojemnik i obsługuje te funkcje, a zniszczenie narzędzia w ten sposób, i odniesienie licząc w ten sposób, i kopiując ten sposób, a ... . Wiele z tych konstrukcji musi być na miejscu i często jest to logiczne miejsce na ich umieszczenie. to, czy chcesz uwzględnić to jako czas, czy nie, zależy od twojej definicji (implementacja musi istnieć podczas łączenia się z tymi apis, tak?).

po tym będziesz musiał wziąć pod uwagę pamięć i własność. w systemie niższego poziomu jest to dobre i konieczne, ale może zająć trochę czasu i rusztowań, aby wdrożyć sposób przenoszenia rzeczy. nie widzę w tym bólu, ponieważ jest to wymóg systemu niższego poziomu. własność, kontrola i odpowiedzialność są oczywiste.

dzięki czemu możemy zwrócić się w stronę api opartych na c, które używają typów nieprzezroczystych: nasze kontenery pozwalają nam wyodrębnić wszystkie małe szczegóły implementacji zarządzania czasem życia i kopiowaniem tych nieprzezroczystych typów, co ostatecznie sprawia, że ​​zarządzanie zasobami jest bardzo proste i oszczędza czas, defekty, i zmniejsza liczbę wdrożeń.

jest naprawdę bardzo prosty w użyciu - problem (pochodzący z GC) polega na tym, że musisz teraz wziąć pod uwagę czas życia swoich zasobów. jeśli pomylisz się, rozwiązanie może zająć dużo czasu. Uczenie się i integrowanie jawnego zarządzania przez całe życie jest zrozumiałe w porównaniu (nie dla wszystkich ludzi) - to prawdziwa przeszkoda. gdy czujesz się komfortowo, kontrolując czasy życia i stosując dobre rozwiązania, naprawdę bardzo łatwo jest zarządzać czasem życia zasobów. to nie jest znacząca część mojego dnia (chyba że wkradł się trudny błąd).

jeśli nie używasz kontenerów (wskaźnik automatyczny / wspólny), to po prostu błagasz o ból.

wdrożyłem własne biblioteki. Zaimplementowanie tych rzeczy zajmuje mi trochę czasu, ale większość ludzi używa ich ponownie (co zwykle jest dobrym pomysłem).


1

Chcesz ręcznie zwolnić pamięć, zamknąć pliki i tego rodzaju rzeczy? Jeśli tak, powiem minimum i zwykle mniej niż większość innych języków, których używałem, zwłaszcza jeśli uogólnimy to nie tylko na „zarządzanie pamięcią”, ale „zarządzanie zasobami”. W tym sensie uważam, że C ++ wymaga mniej ręcznego zarządzania zasobami niż, powiedzmy, Java lub C #.

Wynika to głównie z destruktorów, które automatyzują niszczenie zasobu (pamięci lub w inny sposób). Zazwyczaj jedyny czas, kiedy muszę ręcznie zwolnić / zniszczyć zasób w C ++, to jeśli implementuję strukturę danych na niskim poziomie (coś, czego większość ludzi nie musi robić) lub używam interfejsu API C, w którym spędzam trochę czasu pakowanie zasobu C, który należy ręcznie uwolnić / zniszczyć / zamknąć, w opakowanie C ++ zgodne z RAII.

Oczywiście, jeśli użytkownik poprosi o zamknięcie obrazu w oprogramowaniu do edycji obrazów, muszę usunąć obraz z kolekcji lub czegoś takiego. Ale miejmy nadzieję, że nie liczy się to jako zarządzanie pamięcią lub zasobami, które ma znaczenie w tym kontekście, ponieważ jest to prawie wymagane w każdym języku, jeśli chcesz zwolnić pamięć związaną z tym obrazem w tym czasie. Ale znowu wszystko, co musisz zrobić, to usunąć obraz z kolekcji, a destruktor obrazu zajmie się resztą.

Tymczasem jeśli porównam, powiedzmy, Java lub C #, często ludzie muszą ręcznie zamykać pliki, ręcznie odłączać gniazda, ustawiać odwołania do obiektów na zero, aby umożliwić ich zbieranie śmieci itp. Istnieje o wiele więcej ręcznej pamięci i zarządzanie zasobami w tych językach, jeśli mnie pytasz. W C ++ często nie potrzebujesz nawet unlockmuteksu ręcznie, ponieważ blokada mutexów zrobi to za ciebie automatycznie, gdy muteks wyjdzie poza zakres. Na przykład nigdy nie powinieneś robić takich rzeczy w C ++:

System.IO.StreamReader file = new System.IO.StreamReader(path);
try
{
    file.ReadBlock(buffer, index, buffer.Length);
}
catch (System.IO.IOException e)
{
    ...
}
finally
{
    if (file != null)
        file.Close();
}

W C ++ nie trzeba robić rzeczy takich jak ręczne zamykanie plików. Ostatecznie zamykają się automatycznie, gdy tylko wyjdą poza zakres, niezależnie od tego, czy wychodzą poza zakres, czy są to zwykłe lub wyjątkowe ścieżki wykonania. Podobnie jest w przypadku zasobów związanych z pamięcią, takich jak std::vector. Taki kod jak file.Close()wyżej często byłby źle oceniany, zwłaszcza w kontekście finallybloku, co sugeruje, że zasoby lokalne muszą zostać uwolnione ręcznie, gdy cały sposób myślenia wokół C ++ ma to zautomatyzować.

Jeśli chodzi o ręczne zarządzanie pamięcią, powiedziałbym, że C wymaga maksimum, Java / C # średnio, a C ++ minimum spośród nich. Istnieje wiele powodów, dla których należy nieco unikać używania C ++, ponieważ jest to bardzo trudny do opanowania język, ale zarządzanie pamięcią nie powinno być jednym z nich. Wręcz przeciwnie, uważam, że jest to jeden z najłatwiejszych języków w tym jednym aspekcie.

Oczywiście C ++ pozwala na ręczne przydzielanie pamięci i wywoływanie operator delete/delete[]ręcznie zwolnionej pamięci. Pozwala także korzystać z funkcji C, takich jak mallocifree. Ale to są praktyki kodowania w starożytnym stylu, które, jak sądzę, stały się przestarzałe na długo przed tym, zanim ludzie zaczęli przypisywać sobie uznanie, ponieważ Stroustrup opowiadał się za RAII, zanim nawet ukuł ten termin od samego początku. Więc nawet nie sądzę, że można powiedzieć, że „nowoczesne C ++” automatyzuje zarządzanie zasobami, ponieważ od początku miał to być cel. W przeciwnym razie nie można praktycznie uzyskać bezpieczeństwa wyjątkowego. Po prostu wielu mylących programistów na początku lat 90. próbowało używać C ++ jako C z obiektami, często całkowicie ignorując obsługę wyjątków i nigdy nie powinno się go używać w ten sposób. Jeśli używasz C ++ w sposób, w jaki praktycznie zawsze był przeznaczony do użycia, zarządzanie pamięcią jest całkowicie zautomatyzowane i generalnie nie jest czymś, z czym musisz sobie poradzić (lub powinieneś mieć do czynienia) w ogóle.


1
Współczesna Java ma „spróbuj z zasobami”, które usuwają cały ten niechlujny kod w ostatnim bloku. Rzadko trzeba mieć wreszcie blok. Wygląda na to, że projektanci skopiowali koncepcję RAII.
kiwiron

0

Zależy od starszych kierowników technicznych w zespole. W niektórych firmach (w tym kopalniach) nie ma koncepcji o nazwie smart poiner. Uważa się to za fantazyjne. Tak więc ludzie po prostu umieszczają usuwanie w dowolnym miejscu, a co 2 miesiące istnieje napęd do usuwania wycieków pamięci. Nowa fala instrukcji usuwania pojawia się wszędzie. Zależy więc od firmy i ludzi, którzy tam pracują.


1
Czy w twoim otoczeniu jest coś, co powstrzymuje cię przed używaniem auto_ptri przyjaciółmi?
Sean McMillan,

2
brzmi jak twoja firma nie pisze kodu C ++, piszesz C.
gbjbaanb
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.