2010-05-12 8 views
11

Ich habe C bei einigen Projekten für einen Master-Abschluss benutzt, aber noch nie eine Produktionssoftware damit gebaut. (.NET & Javascript sind mein Brot und Butter.) Offensichtlich ist die Notwendigkeit, free() Speicher, dass Sie malloc() in C kritisch ist. Das ist in Ordnung, gut und gut, wenn Sie beide in einer Routine tun können. Aber während Programme wachsen und Strukturen sich vertiefen, behalten wir den Überblick darüber, was malloc ist, wo und was angemessen ist, wird immer schwieriger.Das malloc zähmen/free beast - tipps & tricks

Ich habe mich auf den Interwebs umgesehen und nur ein paar allgemeine Empfehlungen dafür gefunden. Ich vermute, dass einige von euch, langjährige C-Programmierer, eure eigenen Muster und Praktiken entwickelt haben, um diesen Prozess zu vereinfachen und das Böse vor euch zu halten.

Also: wie empfehlen Sie, Ihre C-Programme so zu strukturieren, dass dynamische Zuweisungen nicht zu Speicherlecks werden?

+4

Dieser lange Zeit C-Codierer wechselte zu C++, und das Problem ging weg. –

+1

@Neil Butterworth: Entweder das oder (öfter) das Problem wurde viel schlimmer. – sharptooth

+1

@sharptooth: Ich bin kein Fan von C++, aber es kümmert sich um eine ganze Reihe von Speicherverwaltungsproblemen für Sie. Ich lasse natürlich die wirklich harten zurück. :-) – JesperE

Antwort

8

Design durch Vertrag. Stellen Sie sicher, dass jeder Funktionskommentar explizit seine Speicherhygiene festlegt - das heißt, ob er mallocs ist und wessen Verantwortung es ist, das zugeteilte freizugeben, und ob er alles übernommene Eigentum übernimmt. Und mit Ihren Funktionen konsistent sein.

Zum Beispiel Ihrer Header-Datei etwas ähnliches enthalten könnte:

/* Sets up a new FooBar context with the given frobnication level. 
* The new context will be allocated and stored in *rv; 
* call destroy_foobar to clean it up. 
* Returns 0 for success, or a negative errno value if something went wrong. */ 
int create_foobar(struct foobar** rv, int frobnication_level); 

/* Tidies up and tears down a FooBar context. ctx will be zeroed and freed. */ 
void destroy_foobar(struct foobar* ctx); 

ich den Rat herzlich billigen Valgrind zu verwenden, ist es ein wirklich fantastisches Werkzeug für Speicherlecks aufzuspüren und ungültige Speicherzugriffe. Wenn Sie nicht unter Linux laufen, ist Electric Fence ein ähnliches Tool, allerdings weniger funktional.

+0

Ich mag das. Ich kann sehen, dass es in vielen Situationen gut funktioniert. – roufamatic

+2

Sie könnten in Erwägung ziehen, destroy_foobar (struct foobar ** ctx) zu erstellen, damit Sie auch den Zeigerwert an der Quelle NULLEN können. Es bietet auch Symmetrie zwischen den Aufrufen: struct foobar * myFoobar; create_foobar (& meinFoobar, 0); Sachen machen(); destroy_foobar (& meinFoobar); Beide Anrufe sehen ähnlich aus. – jmucchiello

+0

FYI, ich benutzte diesen Ansatz + jmucchiellos symmetrischen Destructor und benutzte Valgrind, um meine Arbeit zu überprüfen. Noch ein kleines bisschen zu gehen, aber nur mit diesem Ansatz mit Strenge reduziert meinen Speicherbedarf bei Abschluss um 2 MB. Oder wie die Kinder sagen, w00t! – roufamatic

3

Ich habe Valgrind gefunden, um eine enorme Hilfe bei der Aufrechterhaltung meiner Speicherverwaltung zu sein. Es wird Ihnen sagen, wo Sie auf Speicher zugreifen, der nicht zugewiesen wurde und wo Sie vergessen, Speicher (und eine ganze Reihe von Dingen) freizugeben.

Es gibt auch Möglichkeiten der Speicherverwaltung auf höherer Ebene in C, z. B. meine Verwendung von Speicherpools (siehe z. B. Apache APR).

+1

+1: Valgrind ist sehr nützlich – tur1ng

4

Es wird nicht narrensicher sein (aber das ist wahrscheinlich mit C zu erwarten), und es könnte schwierig sein, mit viel vorhandenen Code zu tun, aber es hilft, wenn Sie Ihren Code eindeutig dokumentieren und immer genau angeben, wem zugeteilt Erinnerung und wer ist verantwortlich für die Freigabe (und mit welchem ​​Allokator/Deallokator). Scheuen Sie sich auch nicht, goto zu verwenden, um ein Single-Entry-/Single-Exit-Idiom für nicht-triviale Ressourcen-allokierende Funktionen zu erzwingen.

5

Große Projekte verwenden häufig die "Pool" -Technik: Dabei ist jede Zuordnung einem Pool zugeordnet und wird automatisch freigegeben, wenn sich der Pool befindet. Dies ist sehr praktisch, wenn Sie eine komplizierte Verarbeitung mit einem einzigen temporären Pool durchführen können, der dann auf einen Schlag freigegeben werden kann, wenn Sie fertig sind. Subpools sind normalerweise möglich; Sie sehen oft ein Muster wie zum Beispiel:

void process_all_items(void *items, int num_items, pool *p) 
{ 
    pool *sp = allocate_subpool(p); 
    int i; 

    for (i = 0; i < num_items; i++) 
    { 
     // perform lots of work using sp 

     clear_pool(sp); /* Clear the subpool for each iteration */ 
    } 
} 

Es macht die Dinge viel einfacher mit String-Manipulation. String-Funktionen würden ein Pool-Argument verwenden, in dem sie ihren Rückgabewert zuweisen würden, der auch der Rückgabewert ist.

Die Nachteile sind:

  • Die zugewiesenen Lebensdauer eines Objekts kann etwas länger sein, da sie für den Pool warten müssen gelöscht oder freigegeben werden.
  • Sie übergeben schließlich ein zusätzliches Poolargument an Funktionen (irgendwo, damit sie alle Zuordnungen vornehmen können, die sie benötigen).
+0

+1, das ist die Art von Ding, die alleine schwer zu lernen ist! – roufamatic

2

Zusammenfassung Zuteilungen und Deallocators für jeden Typ. eine Typdefinition

gegeben
typedef struct foo 
{ 
    int x; 
    double y; 
    char *z; 
} Foo; 

eine allocator Funktion

Foo *createFoo(int x, double y, char *z) 
{ 
    Foo *newFoo = NULL; 
    char *zcpy = copyStr(z); 

    if (zcpy) 
    { 
    newFoo = malloc(sizeof *newFoo); 
    if (newFoo) 
    { 
     newFoo->x = x; 
     newFoo->y = y; 
     newFoo->z = zcpy; 
    } 
    } 
    return newFoo; 
} 

eine Kopierfunktion

Foo *copyFoo(Foo f) 
{ 
    Foo *newFoo = createFoo(f.x, f.y, f.z); 
    return newFoo; 
} 

und eine deallocator Funktion

void destroyFoo(Foo **f) 
{ 
    deleteStr(&((*f)->z)); 
    free(*f); 
    *f = NULL; 
} 

Beachten Sie, dass createFoo() wiederum ruft eine erstellen copyStr() Funktion, die für das Zuweisen von Speicher für und das Kopieren des Inhalts einer Zeichenfolge verantwortlich ist. Beachten Sie auch, dass, wenn copyStr() fehlschlägt und einen NULL zurückgibt, newFoo nicht versucht, Speicher zuzuweisen und einen NULL zurückzugeben. In ähnlicher Weise ruft destroyFoo() eine Funktion auf, um den Speicher für z vor dem Freigeben des Rests der Struktur zu löschen. Schließlich setzt destroyFoo() den Wert von f auf NULL.

Der Schlüssel hier ist, dass der Allokator und Deallocator die Verantwortung für andere Funktionen delegieren, wenn Elementelemente auch Speicherverwaltung erfordern. So, wie Sie Ihre Arten komplizierter, können Sie diese Verteilern wie so wiederverwenden:

typedef struct bar 
{ 
    Foo *f; 
    Bletch *b; 
} Bar; 

Bar *createBar(Foo f, Bletch b) 
{ 
    Bar *newBar = NULL; 
    Foo *fcpy = copyFoo(f); 
    Bletch *bcpy = copyBar(b); 

    if (fcpy && bcpy) 
    { 
    newBar = malloc(sizeof *newBar); 
    if (newBar) 
    { 
     newBar->f = fcpy; 
     newBar->b = bcpy; 
    } 
    } 
    else 
    { 
    free(fcpy); 
    free(bcpy); 
    } 

    return newBar; 
} 

Bar *copyBar(Bar b) 
{ 
    Bar *newBar = createBar(b.f, b.b); 
    return newBar; 
} 

void destroyBar(Bar **b) 
{ 
    destroyFoo(&((*b)->f)); 
    destroyBletch(&((*b)->b)); 
    free(*b); 
    *b = NULL; 
} 

Offensichtlich ist dieses Beispiel geht davon aus, dass die Mitglieder nicht über eine Lebensdauer außerhalb ihrer Container. Das ist nicht immer der Fall, und Sie müssen Ihre Schnittstelle entsprechend gestalten. Dies sollte Ihnen jedoch einen Eindruck vermitteln, was zu tun ist.

Dadurch können Sie Speicher für Objekte in einer konsistenten, wohldefinierten Reihenfolge zuordnen und freigeben, was 80% des Kampfes in der Speicherverwaltung ausmacht. Die anderen 20% stellen sicher, dass jeder Allocator-Aufruf durch einen Deallocator ausgeglichen wird, welcher der wirklich harte Teil ist.

bearbeiten

die Anrufe an die delete* Funktionen verändert, so dass ich die richtigen Typen vorbei bin.