2009-04-28 12 views
34

Ich, und ich denke, viele andere, hatten großen Erfolg mit intelligenten Zeigern, um unsichere Speicheroperationen in C++ einzupacken, mit Dingen wie RAII und so weiter. Die Wrapping-Speicherverwaltung ist jedoch einfacher zu implementieren, wenn Sie Destruktoren, Klassen, Operatorüberladung usw. haben.Smart Pointer/sichere Speicherverwaltung für C?

Für jemanden, der im rohen C99 schreibt, wo könnten Sie zeigen (kein Wortspiel beabsichtigt), um mit der sicheren Speicherverwaltung zu helfen?

Danke.

Antwort

12

Es ist schwierig, intelligente Zeiger in rohen C zu behandeln, da Sie nicht über die Sprachsyntax verfügen, um die Verwendung zu sichern. Die meisten der Versuche, die ich gesehen habe, funktionieren nicht wirklich, da Sie nicht die Vorteile haben, dass Destruktoren ausgeführt werden, wenn Objekte den Gültigkeitsbereich verlassen, was Smartpointer wirklich funktioniert.

Wenn Sie sich wirklich Sorgen machen, sollten Sie in Betracht ziehen, direkt eine garbage collector zu verwenden und die Smart Pointer-Anforderung vollständig zu umgehen.

+0

Wie funktioniert es? Wie kann es die Zeigerzuordnungen verfolgen? – Calmarius

+0

@Calmarius Es gibt verschiedene Möglichkeiten, wie sie funktionieren. Siehe: http://en.wikipedia.org/wiki/Garbage_collection_(computer_science) –

+0

Ich sehe. Ich habe nach dem GC gefragt, den du verlinkt hast. Es behauptet, es funktioniert auf nicht modifizierten C-Programmen, indem nur die malloc und realloc ersetzt. Aber wie findet es die Zeiger, die auf den zugewiesenen Block zeigen? Sie können im Programm kopiert werden. – Calmarius

3

Statische Code-Analyse-Tools wie splint oder Gimpel PC-Lint kann hier helfen - Sie können sogar diese mäßig "präventiv" machen, indem Sie sie in Ihren automatischen Build-Server mit "kontinuierlicher Integration" einbinden. (Sie haben zu tun einer von denen, rechts: grinsen :)

Es gibt andere (etwas teurer) Varianten zu diesem Thema zu ...

+0

+1 Guter Aufruf der statischen Codeüberprüfungstools. –

2

Wenn Sie in Win32 codieren Sie könnten in der Lage sein zu verwenden, structured exception handling etwas Ähnliches zu erreichen.

foo() { 
    myType pFoo = 0; 
    __try 
    { 
     pFoo = malloc(sizeof myType); 
     // do some stuff 
    } 
    __finally 
    { 
     free pFoo; 
    } 
} 

Zwar nicht ganz so einfach wie RAII, können Sie alle Ihre Bereinigungscode an einem Ort sammeln und sicherzustellen, dass es ausgeführt wird: Man könnte so etwas tun.

8

Ein anderer Ansatz, den Sie in Betracht ziehen sollten, ist der Pooled Memory-Ansatz Apache uses. Dies funktioniert besonders gut, wenn Sie dynamische Speicherauslastung haben, die mit einer Anforderung oder einem anderen kurzlebigen Objekt verknüpft ist. Sie können in Ihrer Anfragestruktur einen Pool erstellen und sicherstellen, dass Sie immer Speicher aus dem Pool zuweisen und anschließend den Pool freigeben, wenn Sie die Anfrage bearbeitet haben. Es klingt nicht annähernd so mächtig wie es ist, wenn Sie es ein wenig benutzt haben. Es ist fast so schön wie RAII.

4

Sie können keine intelligenten Zeiger in C verwenden, da sie nicht die erforderliche Syntax bereitstellen, aber Sie können Lecks mit Übung vermeiden. Schreiben Sie den Ressourcenfreigabecode sofort nach der Zuweisung. Also, wenn Sie eine malloc schreiben, sollten Sie die entsprechende free sofort in einem Bereinigung Abschnitt schreiben.

In CI finden Sie in der ‚GOTO Bereinigung‘ Muster eine Menge:

int foo() 
{ 
    int *resource = malloc(1000); 
    int retVal = 0; 
    //... 
    if (time_to_exit()) 
    { 
     retVal = 123; 
     goto cleanup; 
    } 
cleanup: 
    free(resource); 
    return retVal; 
} 

In C auch eine Menge von Kontexten verwendet werden, die wir Sachen zuweisen, kann die gleiche Regel für das auch angewendet werden:

int initializeStuff(Stuff *stuff) 
{ 
    stuff->resource = malloc(sizeof(Resource)); 
    if (!stuff->resource) 
    { 
     return -1; ///< Fail. 
    } 
    return 0; ///< Success. 
} 

void cleanupStuff(Stuff *stuff) 
{ 
    free(stuff->resource); 
} 

Dies ist analog zu den Objektkonstruktoren und Destruktoren. Solange Sie die zugewiesenen Ressourcen nicht an andere Objekte weitergeben, wird es nicht undicht und Zeiger werden nicht baumeln.

Es ist nicht schwer, einen benutzerdefinierten Zuordner zu schreiben, der Zuweisungen verfolgt und undichte Blöcke atexit schreibt.

Wenn Sie Zeiger auf die zugewiesenen Ressourcen verschenken müssen, können Sie Wrapperkontexte dafür erstellen, und jedes Objekt besitzt einen Wrapperkontext anstelle der Ressource. Diese Wrapper teilen sich die Ressource und ein Counter-Objekt, das die Nutzung verfolgt und die Objekte freigibt, wenn niemand sie benutzt. So funktioniert C++ 11's shared_ptr und weak_ptr. Es wird hier genauer beschrieben: How does weak_ptr work?

2

Sie können Makros definieren, zum Beispiel BEGIN und END, die anstelle von geschweiften Klammern verwendet werden und die automatische Zerstörung von Ressourcen auslösen, die ihren Gültigkeitsbereich verlassen. Dies erfordert, dass auf alle diese Ressourcen durch intelligente Zeiger verwiesen wird, die auch einen Zeiger auf den Destruktor des Objekts enthalten. In meiner Implementierung behalte ich einen Stapel von intelligenten Zeigern im Heap-Speicher, merke den Stapelzeiger beim Eintritt in einen Geltungsbereich und rufe Destruktoren aller Ressourcen über dem gespeicherten Stapelzeiger am Bereichsexit auf (END oder Makroersatz für die Rückkehr). Dies funktioniert auch dann gut, wenn der Ausnahmemechanismus setjmp/longjmp verwendet wird, und bereinigt alle Zwischenbereiche zwischen dem Catch-Block und dem Bereich, in dem die Ausnahme ausgelöst wurde. Siehe https://github.com/psevon/exceptions-and-raii-in-c.git für die Implementierung.

12

Die Frage ist ein bisschen alt, aber ich dachte mir, ich würde mir die Zeit nehmen, um meine GNU-Compiler (GCC, Clang, ICC, MinGW, ...) auf meine smart pointer library zu verbinden.

Diese Implementierung basiert auf der Bereinigung Variable Attribut, eine GNU-Erweiterung, um automatisch den Speicher freizugeben, wenn außerhalb des Gültigkeitsbereichs gehen, und als solche ist nicht ISO C99, aber C99 mit GNU-Erweiterungen.

Beispiel:

simple1.c:

#include <stdio.h> 
#include <csptr/smart_ptr.h> 

int main(void) { 
    smart int *some_int = unique_ptr(int, 1); 

    printf("%p = %d\n", some_int, *some_int); 

    // some_int is destroyed here 
    return 0; 
} 

Compilation & Valgrind Sitzung:

$ gcc -std=gnu99 -o simple1 simple1.c -lcsptr 
$ valgrind ./simple1 
==3407== Memcheck, a memory error detector 
==3407== Copyright (C) 2002-2013, and GNU GPL\'d, by Julian Seward et al. 
==3407== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info 
==3407== Command: ./simple1 
==3407== 
0x53db068 = 1 
==3407== 
==3407== HEAP SUMMARY: 
==3407==  in use at exit: 0 bytes in 0 blocks 
==3407== total heap usage: 1 allocs, 1 frees, 48 bytes allocated 
==3407== 
==3407== All heap blocks were freed -- no leaks are possible 
==3407== 
==3407== For counts of detected and suppressed errors, rerun with: -v 
==3407== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) 
0
Sometimes i use this approach and it seems good :) 

Object *construct(type arg, ...){ 

    Object *__local = malloc(sizeof(Object)); 
    if(!__local) 
     return NULL; 
    __local->prop_a = arg; 
    /* blah blah */ 


} // constructor 

void destruct(Object *__this){ 

    if(__this->prop_a)free(this->prop_a); 
    if(__this->prop_b)free(this->prop_b); 

} // destructor 

Object *o = __construct(200); 
if(o != NULL) 
    ;; 

// use 

destruct(o); 

/* 
    done ! 
*/