Wie viel Programmiererzeit wird in C ++ für die Speicherverwaltung aufgewendet?

39

Menschen, die es gewohnt sind, gesammelte Sprachen zu löschen, haben häufig Angst vor der Speicherverwaltung in C ++. Es gibt Werkzeuge, wie auto_ptrund shared_ptrwelche viele der Speicherverwaltungsaufgaben für Sie behandelt. Viele C ++ - Bibliotheken sind älter als diese Tools und können die Speicherverwaltungsaufgaben auf ihre eigene Weise ausführen.

Wie viel Zeit verbringen Sie mit Speicherverwaltungsaufgaben?

Ich vermute, dass dies in hohem Maße von der Menge der von Ihnen verwendeten Bibliotheken abhängt. Sagen Sie daher bitte, auf welche Bibliotheken Ihre Antwort zutrifft und ob sie diese verbessern oder verschlechtern.

Sean McMillan
quelle
1
Nicht so viel, wirklich ... Besonders mit C ++ 0x, Referenzen und AWL. Sie können sogar Code ohne Speicherverwaltung schreiben.
Coder
9
Im Allgemeinen: Nicht so viel, wenn Sie erfahren sind. Eine Menge, wenn Sie Anfänger in C ++ sind (-> normalerweise nach Speicher- / Ressourcenlecks suchen).
3.
1
Ich finde die eigentliche Frage heutzutage eher, ob es darum geht, veraltete Referenzen aufzuspüren. Und es ist normalerweise jedes Mal ziemlich offensichtlich, nur ärgerlich, dass es vorher nicht gefangen wurde: p
Matthieu M.
Ich weiß, dass dies alt ist, aber das IMO-Speichermanagement ist ein wesentlicher Bestandteil eines guten Programmierers. Abstraktionen wie die STL-Container sind nett, aber Unkenntnis des Gedächtnisses widerspricht der eigentlichen Idee der Berechnung. Man könnte genauso gut fragen, wie man algebraische Manipulationen, Logik und Schleifen aus dem Arsenal des Programmierers entfernen kann.
Imallett
Wie wäre es mit "Wie viel Zeit wird zum Debuggen der fehlgeschlagenen Speicherverwaltung benötigt?" An sich ist Speicherverwaltung in C ++ möglich und nicht so schwer. Fakt ist: Es ist ein präzises Handwerk, und es ist sehr anfällig für Mist. Wenn Sie es vermasseln, werden Sie es vielleicht gar nicht bemerken, und das Zurückverfolgen alter Fehler mit unberechenbarem Verhalten, das sich im Laufe der Zeit häufte, ist die Echtzeitsenke, vor der Sie Angst haben sollten. Das ist der Grund, warum die modernen, nicht überflüssig gesammelten Sprachen (ich denke an Rost) dem Compiler viel Verantwortung für die Überprüfung typischer Fehler übertragen haben.
ZJR

Antworten:

54

In modernem C ++ müssen Sie sich erst um die Speicherverwaltung kümmern, wenn Sie dies tun müssen. Dies gilt vor allem für Optimierungszwecke oder wenn der Kontext Sie dazu zwingt (denken Sie an Hardware mit großen Einschränkungen). Ich habe ganze Spiele geschrieben, ohne das rohe Gedächtnis zu manipulieren, und mich nur darum gekümmert, Container zu verwenden, die das richtige Werkzeug für den Job sind, wie in jeder Sprache.

Es hängt also vom Projekt ab, aber die meiste Zeit müssen Sie nicht mit der Speicherverwaltung befassen, sondern nur mit der Objektlebensdauer. Dies wird mit intelligenten Zeigern gelöst , einem idiomatischen C ++ - Tool, das aus RAII resultiert .

Sobald Sie RAII verstanden haben , ist die Speicherverwaltung kein Problem mehr.

Wenn Sie dann auf den Raw-Speicher zugreifen müssen, tun Sie dies in sehr spezifischem, lokalisiertem und identifizierbarem Code, wie bei Pool-Objekt-Implementierungen, nicht "überall".

Außerhalb dieser Art von Code müssen Sie nicht den Speicher manipulieren, sondern nur die Lebensdauer der Objekte.

Der "schwierige" Teil ist, RAII zu verstehen.

Klaim
quelle
10
Absolut wahr. In den letzten 5 Jahren habe ich nur "delete" geschrieben, wenn ich mit altem Code gearbeitet habe.
drxzcl
3
Ich arbeite in einer stark auf Stapelgröße beschränkten eingebetteten Umgebung. So cool RAII auch ist, es funktioniert nicht gut, wenn der Stapelplatz knapp ist. Es geht also wieder um Zeigermikromanagement.
Bastibe
1
@nikie Ich verwende die intelligenten Zeiger der Bibliotheken im Code, die ihre API manipulieren, und verwende dann standardmäßige oder verstärkte intelligente Zeiger im Code, der für meine Anwendung spezifisch ist (wenn ich derjenige bin, der darüber entscheidet). Wenn Sie in einigen Modulen Bibliothekscode isolieren können, der die Verwendung in Ihrer Anwendung abstrahiert, vermeiden Sie die API-Belastung durch Abhängigkeiten.
Klaim
12
@Paperflyer: RAII beansprucht nicht mehr Stapelspeicher als deletemanuell, es sei denn, Sie haben eine beschissene Implementierung.
DeadMG
2
@Paperflyer: Der Smart Pointer auf dem Heap belegt den gleichen Platz. Der Unterschied besteht darin, dass der Compiler den Resource-Freigabecode bei allen Exits einer Funktion einfügt. Und da dies so weit verbreitet ist, ist es in der Regel gut optimiert (z. B. das Zusammenfalten mehrerer Exits auf eine Weise, die Sie nicht können - Sie können keinen Code nach einem setzen return).
MSalters
32

Speicherverwaltung wird verwendet, um Kinder zu erschrecken, aber es ist nur eine Art von Ressource, um die sich ein Programmierer kümmern muss. Denken Sie an Dateihandles, Netzwerkverbindungen und andere Ressourcen, die Sie vom Betriebssystem erhalten.

Die Sprachen, die die Garbage Collection unterstützen, ignorieren in der Regel nicht nur die Existenz dieser Ressourcen, sondern erschweren auch deren ordnungsgemäßen Umgang, indem sie keinen Destruktor bereitstellen.

Kurz gesagt, ich würde nicht vorschlagen, dass ein C ++ - Entwickler viel Zeit damit verbringt, sich um die Speicherverwaltung zu kümmern. Wie die Antwort von klaim zeigt, ist der Rest nur ein Reflex, sobald Sie RAII in den Griff bekommen.

Dysaster
quelle
3
Mir gefällt besonders, wie HttpWebRequest.GetResponse in GC-Sprachen funktioniert und abstürzt. GC ist cool, bis es anfängt zu saugen, weil immer noch Ressourcen auslaufen. msdn.microsoft.com/en-us/library/… Siehe "Achtung".
Coder
6
+1 zum Anzeigen des Speichers als Ressource. Legacy-Code oder nicht, wie oft müssen wir laut rufen: Speicherverwaltung ist eine Fähigkeit und kein Fluch .
Aquaherd
4
@Coder Bin mir nicht sicher, ob ich folge .. GC ist zum Kotzen, weil es sowieso möglich ist, Ressourcen zu missbrauchen ..? Ich denke, dass C # einen guten Job macht, indem es mit IDisposable deterministische Ressourcen freigibt ...
Max
8
@Max: Weil, wenn es Müll gesammelt hat, dann erwarte ich, dass ich mir keine Sorgen über dumme Ressourcen mache, indem ich und benutzerdefinierte IDisposables verwende. Ressourcen haben den Geltungsbereich verlassen, das wars, sie sollten gereinigt werden. In der Realität muss ich allerdings noch überlegen, welche auslaufen und welche nicht. Schlägt jeden Grund aus, die GC-Sprache zu verwenden.
Coder
5
@deadalnix Sie haben das finalizeKonstrukt. Sie wissen jedoch nicht, wann es aufgerufen wird. Wird es sein, bevor Ihnen die Sockets oder WebResponse-Objekte ausgehen? In zahlreichen Artikeln erfahren Sie, auf welche Sie sich nicht verlassen sollten finalize- aus gutem Grund.
Dysaster
13

So ziemlich keine. Selbst mit alten Technologien wie COM können Sie benutzerdefinierte Deleter für die Standard-Zeiger schreiben, die sie in kürzester Zeit konvertieren. Zum Beispiel std::unique_ptrkann einen COM - Verweis mit fünf Zeilen eines benutzerdefinierten deleter eindeutig halten umgewandelt werden. Selbst wenn Sie Ihren eigenen Ressourcen-Handler manuell schreiben müssen, macht es die Verbreitung von Wissen wie SRP und Copy-and-Swap relativ einfach, eine Ressourcen-Verwaltungsklasse zu schreiben, die für immer mehr verwendet werden kann.

Die Realität ist, dass Ihr C ++ 11-Compiler gemeinsam genutzt wird, einzigartig ist und keine Eigentumsrechte besitzt. Sie müssen nur kleine Adapter schreiben, damit sie auch mit altem Code funktionieren.

DeadMG
quelle
1
Wie viel Erfahrung mit C ++ müssen Sie haben, um a) einen benutzerdefinierten Deleter zu schreiben, b) zu wissen, dass Sie einen benutzerdefinierten Deleter benötigen? Ich frage, weil es einfach zu sein scheint, eine neue GC-Sprache zu finden und nahezu korrekt zu werden, ohne das Ganze zu kennen - ist es einfach, auch in C ++ richtig zu sein?
Sean McMillan
1
@SeanMcMillan: Benutzerdefinierte Deleter lassen sich trivial schreiben und bereitstellen. Der von mir erwähnte COM-Code besteht aus fünf Zeilen für alle COM-Typen, und jeder, der über eine Grundausbildung in modernem C ++ verfügt, sollte mit ihnen vertraut sein. Sie können keine GC-Sprache auswählen, da der GC überraschenderweise keine COM-Objekte sammelt. Oder Dateihandles. Oder Speicher von anderen Systemen erhalten. Oder Datenbankverbindungen. RAII wird all diese Dinge tun.
DeadMG
2
Mit "Eine GC-Sprache auswählen" meine ich, dass ich zwischen Java / C # / Ruby / Perl / Javascript / Python hin- und hergesprungen bin, und dass sie alle den gleichen Ressourcenverwaltungsstil haben - der Speicher ist größtenteils automatisch und alles andere müssen Sie verwalten. Es klingt für mich so, als würden Sie sagen, dass Sie mit den Verwaltungstools von C ++ Dateihandles / db connections / etc genauso wie den Speicher verwalten können, und dass es relativ einfach ist, wenn Sie es erst einmal gelernt haben. Keine Gehirnoperation. Verstehe ich richtig?
Sean McMillan
3
@ SeanMcMillan: Ja, das ist genau richtig und nicht komplex.
DeadMG
11

Als ich (vor langer Zeit) ein C ++ - Programmierer war, machte ich mir lange Gedanken über Speicherverwaltungsfehler, wenn ich versuchte, schwer reproduzierbare Fehler zu beheben .

Mit Modem C ++ ist die Speicherverwaltung weniger ein Problem, aber Sie können jedem in einem großen Team vertrauen, dass er es richtig macht. Was kostet / Zeit von:

  • Schulung (nicht viele Programmierer haben ein gutes Verständnis für die Probleme)
  • Codeüberprüfungen zur Ermittlung von Speicherverwaltungsproblemen
  • Debuggen von Speicherverwaltungsproblemen
  • Denken Sie immer daran, dass ein Fehler in einem Teil der App möglicherweise auf ein Speicherverwaltungsproblem in einem nicht verwandten Teil der App zurückzuführen ist .

Es ist also nicht nur die Zeit, die für das „ Tunaufgewendet wird , dies ist eher ein Problem bei großen Projekten.

Ian
quelle
2
Ich denke, einige C ++ - Projekte haben es verzweifelt, jemals einige ihrer Speicherlecks aufgrund von schlecht geschriebenem Code zu beheben. Schlechter Code wird passieren, und wenn dies der Fall ist, kann dies auch viel Zeit für andere Leute in Anspruch nehmen.
Jeremy
@Jeremy, als ich von C ++ nach C # wechselte, gab es immer noch so viel schlecht geschriebenen Code (wenn nicht mehr), aber es war sehr einfach, den Teil des Programms zu finden, der einen bestimmten Fehler hatte.
Ian
1
Ja, das ist eine Menge, warum die meisten Geschäfte auf Java oder .NET umgezogen sind. Die Speicherbereinigung verringert den unvermeidlichen Schaden durch fehlerhaften Code.
Jeremy
1
Seltsamerweise haben wir diese Probleme nicht.
David Thornley
1
@ DavidThornley, ich denke, ein Großteil des Problems war das Schreiben von UI-Code in C ++. Heutzutage ist der meiste C ++ - Code, den ich sehe, nicht UI
Ian
2

Ich benutze oft Boost- und TR1-Bibliotheken und sie machen die Speicherverwaltung im engeren Sinne (neu / löschen) zu einem Nicht-Problem. Andererseits ist die Speicherzuweisung in C ++ nicht billig, und man muss darauf achten, wo diese ausgefallenen gemeinsamen Zeiger erstellt werden. Sie belegen häufig Arbeitsbereiche oder arbeiten mit stapelbasiertem Speicher. Im Allgemeinen würde ich sagen, dass es sich hauptsächlich um ein Designproblem und nicht um ein Implementierungsproblem handelt.

quant_dev
quelle
2

Wie lange dauert es als Kunde? sehr wenig, sobald Sie den Dreh raus haben. Wenn ein Container die Lebensdauer und Referenzen verwaltet, ist das wirklich sehr einfach. imo, es ist viel einfacher als das manuelle Zählen von Verweisen, und es ist praktisch transparent, wenn Sie den Container, den Sie als Dokumentation verwenden, betrachten, wodurch der Compiler bequem verhindert, dass Sie ungültige Eigentumsübertragungen in einem gut gestalteten typsicheren System durchführen.

Die meiste Zeit, die ich (als Client) verbringe, wird mit Typen von anderen APIs verbracht, sodass sie im Kontext Ihrer Programme gut funktionieren. Beispiel: Dies ist mein ThirdPartyFont-Container. Er unterstützt diese Funktionen und implementiert die Zerstörung auf diese Weise und die Referenzzählung auf diese Weise und das Kopieren auf diese Weise . Viele dieser Konstrukte müssen vorhanden sein, und oft ist es der logische Ort, sie zu platzieren. ob Sie dies als Zeitangabe angeben möchten oder nicht, hängt von Ihrer Definition ab (die Implementierung muss für die Schnittstelle zu diesen APIs ohnehin vorhanden sein, oder?).

Danach müssen Sie Gedächtnis und Besitz berücksichtigen. In einem untergeordneten System ist das gut und notwendig, aber es kann einige Zeit und ein Gerüst dauern, um zu implementieren, wie Sie die Dinge bewegen sollten. ich sehe es nicht als schmerz an, da dies eine anforderung eines untergeordneten systems ist. Eigentum, Kontrolle und Verantwortung sind offensichtlich.

So können wir dies auf c-basierte APIs umstellen, die undurchsichtige Typen verwenden: Mit unseren Containern können wir alle Details der Implementierung zum Verwalten der Lebensdauer und zum Kopieren dieser undurchsichtigen Typen zusammenfassen. Dies macht die Ressourcenverwaltung letztendlich sehr einfach und spart Zeit und Fehler. und reduziert Implementierungen.

Es ist wirklich sehr einfach, diese zu verwenden - das Problem (von GC kommend) ist, dass Sie jetzt die Lebensdauer Ihrer Ressourcen berücksichtigen müssen. Wenn Sie etwas falsch machen, kann die Lösung viel Zeit in Anspruch nehmen. Das Erlernen und Integrieren von explizitem Lebensdauermanagement ist im Vergleich verständlicherweise komplex (nicht für alle Menschen) - das ist die eigentliche Hürde. Wenn Sie erst einmal die Lebensdauern im Griff haben und gute Lösungen verwenden, ist es wirklich sehr einfach, die Lebensdauern von Ressourcen zu verwalten. Es ist kein wesentlicher Teil meines Tages (es sei denn, ein schwieriger Fehler hat sich eingeschlichen).

Wenn Sie keine Container verwenden (Auto / Shared Pointer), bitten Sie nur um Schmerz.

Ich habe meine eigenen Bibliotheken implementiert. es nimmt mir Zeit , um diese Dinge zu implementieren, aber die meisten Menschen wieder verwenden (was in der Regel eine gute Idee ist).

Justin
quelle
1

Sie möchten gerne manuell Speicher freigeben, Dateien schließen, solche Dinge? In diesem Fall würde ich das Minimum und normalerweise weniger als die meisten anderen Sprachen angeben, die ich verwendet habe, insbesondere wenn wir dies nicht nur auf "Speicherverwaltung", sondern auf "Ressourcenverwaltung" verallgemeinern. In diesem Sinne glaube ich, dass C ++ weniger manuelle Ressourcenverwaltung erfordert als beispielsweise Java oder C #.

Dies ist hauptsächlich auf Destruktoren zurückzuführen, die das Zerstören der Ressource (Speicher oder auf andere Weise) automatisieren. Normalerweise muss ich eine Ressource in C ++ nur dann manuell freigeben / zerstören, wenn ich eine Datenstruktur auf Vlow-Ebene implementiere (was die meisten Leute nicht tun müssen) oder eine C-API verwende, in der ich nur ein wenig Zeit verbringe Umhüllen der C-Ressource, die manuell freigegeben / zerstört / geschlossen werden muss, in einen RAII-konformen C ++ - Wrapper.

Wenn ein Benutzer ein Bild in einer Bildbearbeitungssoftware schließen möchte, muss ich das Bild natürlich aus einer Sammlung entfernen. Aber hoffentlich zählt das nicht als "Speicher" - oder "Ressourcen" -Verwaltung, wie es in diesem Zusammenhang von Bedeutung ist, da dies in jeder Sprache so ziemlich erforderlich ist, wenn Sie den Speicher freigeben möchten, der zu diesem Zeitpunkt mit diesem Bild verknüpft ist. Aber alles, was Sie tun müssen, ist, das Bild aus der Sammlung zu entfernen, und der Bildzerstörer kümmert sich um den Rest.

Wenn ich es mit Java oder C # vergleiche, muss man häufig Dateien manuell schließen, Sockets manuell trennen, Objektreferenzen auf null setzen, damit sie vom Müll gesammelt werden können, usw. Es gibt viel mehr manuellen Speicher und Ressourcenverwaltung in diesen Sprachen, wenn Sie mich fragen. In C ++ müssen Sie häufig nicht einmal unlockmanuell einen Mutex erstellen, da der Mutex-Locker dies automatisch für Sie erledigt, wenn der Mutex den Gültigkeitsbereich verlässt. Zum Beispiel sollten Sie in C ++ niemals so etwas tun müssen:

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();
}

Es ist nicht erforderlich, Dateien in C ++ manuell zu schließen. Sie schließen sich automatisch, sobald sie den Gültigkeitsbereich verlassen, unabhängig davon, ob sie das Ergebnis oder normale oder außergewöhnliche Ausführungspfade sind. Ähnliches gilt für speicherbezogene Ressourcen wie std::vector. Ein solcher Code wie file.Close()oben wird oft missbilligt, da er insbesondere im Zusammenhang mit einem finallyBlock darauf hindeutet, dass die lokale Ressource manuell freigegeben werden muss, wenn die gesamte Denkweise in C ++ dies automatisieren soll.

In Bezug auf die manuelle Speicherverwaltung würde ich sagen, dass C das Maximum, Java / C # eine mittlere Menge und C ++ das Minimum von diesen erfordert. Es gibt viele Gründe, etwas schüchtern mit C ++ umzugehen, da es eine sehr schwierig zu beherrschende Sprache ist, aber die Speicherverwaltung sollte keine davon sein. Im Gegenteil, ich denke tatsächlich, dass es in dieser Hinsicht eine der einfachsten Sprachen überhaupt ist.

Natürlich können Sie in C ++ den Speicher manuell zuweisen und aufrufen operator delete/delete[] , um Speicher manuell freizugeben. Sie können auch C-Funktionen wie mallocund verwendenfree. Aber das sind Codierungspraktiken im alten Stil, von denen ich denke, dass sie veraltet sind, lange bevor die Leute Anerkennung finden, da Stroustrup RAII befürwortet hat, bevor er den Begriff überhaupt von Anfang an geprägt hat. Ich halte es also nicht einmal für fair zu sagen, dass "modernes C ++" das Ressourcenmanagement automatisiert, weil dies eigentlich der ganze Zweck sein sollte. Sie können sonst praktisch keine Ausnahmesicherheit erhalten. Es ist nur so, dass viele fehlgeleitete Entwickler in den frühen 90ern versucht haben, C ++ wie C mit Objekten zu verwenden, und dabei die Ausnahmebehandlung oft völlig ignoriert haben, und es sollte nie so verwendet werden. Wenn Sie C ++ so verwenden, wie es eigentlich immer beabsichtigt war, dann ist die Speicherverwaltung vollständig automatisiert und im Allgemeinen nicht etwas, mit dem Sie manuell zu tun haben (oder mit dem Sie zu tun haben sollten).


quelle
1
Das moderne Java hat "try with resources", was den ganzen chaotischen Code im finally-Block entfernt. Es ist selten notwendig, einen finalen Block zu haben. Offenbar haben die Designer das RAII-Konzept kopiert.
Kiwiron
0

Hängt von den technischen Führungskräften im Team ab. In einigen Unternehmen (einschließlich meiner) gibt es kein Konzept namens Smart Poiner. Es gilt als schick. Daher werden Löschvorgänge einfach überall abgelegt, und alle 2 Monate wird ein Laufwerk zur Behebung von Speicherlecks eingerichtet. Überall kommt eine neue Welle von Löschanweisungen an. Kommt also auf die Firma und die Art der Leute an, die dort arbeiten.

Jagannath
quelle
1
Befindet sich in Ihrer Umgebung etwas, das Sie auto_ptrund Ihre Freunde davon abhält, es zu benutzen ?
Sean McMillan
2
Es hört sich so an, als würde Ihr Unternehmen keinen C ++ - Code schreiben, Sie schreiben C.
gbjbaanb