2009-04-06 15 views
12

Ich denke, ich habe ein gutes Verständnis davon, wie man mit Speicher in C++ umgeht, aber es in C zu machen ist anders, ich bin ein bisschen daneben.Der beste Weg, Speicherzuweisung in C zu behandeln?

In C++ habe ich Konstruktoren und Destruktoren, ich habe die ziemlich einfach neu und löschen und ich weiß, wie man es mit RAII verkapselt, mit Smartpointern und innerhalb von Klassen.

Allerdings kann ich in C nicht malloc umgehen und auf die gleiche Weise frei. Ich weiß nicht, wie ich sie verstecken und automatisieren soll. Alles, was ich herausfinden kann, sind Funktionen, um meine Zeiger zu initiieren und zu zerstören. Aber wie sollte ich meine Speicherverwaltung strukturieren?

Während ich dies schreibe, habe ich erkannt, dass es eher eine Frage ist, ob ich den Fluss von C verstehe als alles andere, aber eine Frage nach der anderen.

Bearbeiten: Danke für die Antworten, aber ich muss mich neu formulieren.

Wenn ich sage, dass ich RAII und Smart Pointer für C++ verwende, will ich nicht das gleiche für C, ich weiß, es ist nicht das Gleiche. Aber wie ich die Speicherzuweisung in C++ bearbeite, ist mit diesen Techniken verbunden.

Zum Beispiel in meinen Klassen ich dynamisch hinzufügen und zerstören den Speicher meiner Klasse verwendet. Auf diese Weise kann ich eine Art Kapselung erreichen, ich muss nicht wissen, wann/wie/warum die Klasse ihren Speicher verarbeitet, sie tut es einfach. Dies bedeutet, dass ich die Handhabung des unteren Speichers "verstecken" und mich nur auf einige "größere" Klassen konzentrieren kann.

Was ich wissen möchte ist, was ist die beste Praxis im Umgang mit Speicher in C? Es gibt keine Klassen mit Konstruktoren/Destruktoren, um das für mich zu handhaben. Ist es gut, am Anfang einer Funktion Speicher zuzuordnen oder eine Funktion zu verwenden, die sie für mich erstellt? Und wie sollte ich sie wieder befreien?

Dies sind allgemeine Fragen und sie unterscheiden sich von Situation zu Situation, aber wie gehen Sie am liebsten damit um? Welche Tipps und Lektionen kannst du geben?

Antwort

16

Ein Teil der Verwirrung ist, dass es von Natur aus schwierig in C. malloc und free sind ähnlich wie new und delete: malloc reserviert neuen Speicher und gibt einen Zeiger auf diesen Speicher zurück. free macht diesen Speicher wieder verfügbar, , solange es Speicher ist, der mit malloc zugewiesen wurde. Sonst macht es nur Hash von einem Stück Speicher. Es ist mir egal.

Das Wichtigste bei malloc/free ist, sich für einen disziplinierten Gebrauch zu entscheiden und ihn konsequent aufrechtzuerhalten. Hier sind einige Hinweise:

Überprüfen Sie immer die zurückgegebene Zeiger von malloc für NULL

if((p = (char *) malloc(BUFSIZ)) == NULL { 
    /* then malloc failed do some error processing. */ 
} 

Für Gürtel und Hosenträger Sicherheit, stellen Sie einen Zeiger auf NULL, nachdem sie zu befreien.

free(p); 
p = NULL ; 

versuchen malloc und kostenlos einen Teil des Speichers im gleichen Umfang, wenn möglich:

{ char * p ; 
    if((p = malloc(BUFSIZ)) == NULL { 
     /* then malloc failed do some error processing. */ 
    } 

/* do your work. */ 

    /* now you're done, free the memory */ 

    free(p); 
    p = NULL ; /* belt-and suspenders */ 
} 

Wenn Sie nicht können, deutlich machen, dass das, was Sie Rückkehr ist malloc ‚ed Speicher , damit der Anrufer es freigeben kann.

/* foo: do something good, returning ptr to malloc memory */ 
char * foo(int bar) { 
    return (char *) malloc(bar); 
} 
+0

Sie benötigen die Besetzung nicht (außer wenn Sie einen Aufruf von malloc() direkt als varargs-Funktionsargument übergeben oder Pre-ANSI-Compiler verwenden). Das führt zu einem schlechten Habit, weil es eine fehlende #Include verbirgt. – dirkgently

+0

+1 für malloc/frei im gleichen Bereich und für p = NULL nach free(). malloc/free in demselben Bereich ist eine gute Lokalisierung, und beachte, dass "doing your work" Funktionsaufrufe beinhalten kann, die Zeiger verwenden. Beste Antwort zu dieser Zeit. – dwc

+0

das ist eine andere stilistische Sache, Dirk. Ich bevorzuge die explizite Besetzung, weil das Teil meiner eigenen Typ-Argumentation ist, die ich durchführe, während ich C schreibe. Ich sehe "(char *) malloc" als eine abstrakte Operation, die ein malloksiertes Char-Stück Speicher zurückgibt. –

10

Während ich dies schreibe ich realisiert habe dies ist mehr eine Frage über mich den Fluss von C als etwas anderes zu verstehen, sondern eine Frage auf Zeit.

Ich denke wirklich, dass Sie auf K&R nachlesen sollten, wenn Sie nicht haben.

+0

Warum Leute noch dieses alte Buch vorschlagen? Sicherlich ist es bemerkenswert in der C-Geschichte, geschrieben von seinen Autoren, aber seine letzte Ausgabe ist 20 Jahre alt, behandelt nicht ISO C99 und noch weniger gängige Praktiken, wie OO und Smart Memory Management von Bibliotheken wie GLib (von GNOME). – Juliano

+4

@Juliano: Ich sehe kein Problem damit, ein altes Buch zum Lernen einer alten Sprache zu empfehlen. _Die C-Programmiersprache_ ist bemerkenswert gut geschrieben, und die Leute empfehlen sie immer wieder, weil sie gut lehrt, genau das zu tun, was sie beabsichtigt. Angeblich populäre Praktiken und spezifische Bibliotheken von Drittanbietern sind außerhalb des Umfangs eines Buches über die Sprache selbst. C99 bricht nichts Wesentliches und seine neuen Funktionen werden sowieso nicht von vielen Compilern unterstützt. Wenn Sie versuchen, C zu lernen (und nicht GLib oder OO Praktiken oder ...), gibt es wirklich kein besseres Lehrbuch. –

2

Nun in C müssen Sie alle Sie Speicherverwaltung manuell tun, wie Sie bereits festgestellt haben. Das sollte keine Überraschung sein.

6

Die traurige Wahrheit ist, dass C nicht wirklich entworfen wurde, um alle diese Speicherverwaltungsprobleme einzukapseln.

Wenn Sie sich ziemlich hochwertige APIs wie POSIX ansehen, werden Sie sehen, dass das übliche Muster darin besteht, dass Sie einen Zeiger auf einen Zeiger an eine Funktion übergeben, der dann den Speicher zuweist und Sie ihn später erneut übergeben eine Funktion, die es zerstört.

Es ist nicht unbedingt elegant, aber ich glaube nicht, es gibt viele Möglichkeiten, ohne die Simulation OOP in C

1

Ich weiß nicht, es ist wirklich elegant zu machen, wie sie verstecken und wie die Dinge zu automatisieren .

C und C++ sind verschiedene Sprachen. Nun, sag das hundertmal für dich. Sei laut.

Was meinst du mit verstecken? Was meinst du mit automatisieren? Kannst du ein paar Beispiele veröffentlichen? Warum musst du dich verstecken und/oder automatisieren?

Gute Plätze online C Speicherzuweisung für den Anfang sind:

+0

Aus irgendeinem seltsamen Grund funktioniert Ihr 2. Link nicht: http://doc.cat-v.org/henry_spencer/ten-commandments – Klelky

+0

@Klelky: Sie hatten Recht. Die Verknüpfung wurde korrigiert. – dirkgently

0

Der üblicher Weg

MyType *ptr = malloc(array_size * sizeof *ptr); 

sind Aber wenn Sie mit C++ kompatibel sein wollen, tun Sie

MyType *ptr = (MyType*) malloc(array_size * sizeof *ptr); 

Sie können auch ein Makro

#define MALLOC(NUMBER, TYPE) (TYPE *) malloc(NUMBER * sizeof(TYPE)) 
MyType *ptr = MALLOC(10, MyType); 

Natürlich machen, ohne RAII, sicher, irgendwann später haben Sie

free(ptr); 
-2

Sie mögen denken, dass das, was ich sage, ist seltsam, aber es gibt keinen großen Unterschied zwischen C++ und C C hat RAII auch. RAII gehört nicht nur zu C++.

Einzige Sache, die Sie genug Disziplin haben sollten, um Management zu tun.

C++ Klasse:

class foo { 
public: 
    ofstream ff; 
    int x,y; 
    foo(int _x) : x(_x),y(_x){ ff.open("log.txt"); } 
    void bar() { ff<<x+y<<endl; } 
}; 

int main() 
{ 
    auto_ptr<foo> f(new foo); 
    f->bar(); 
} 

C Objekt

typedef struct FOO { 
    FILE *ff; 
    int x,y; 
} *foo_t; 

foo_t foo_init(int x) 
{ 
    foo_t p=NULL; 
    p=malloc(sizeof(struct FOO)); // RAII 
    if(!p) goto error_exit; 
    p->x=x; p->y=x; 
    p->ff=fopen("log.txt","w"); // RAII 
    if(!p->ff) goto error_exit; 
    return p; 
error_exit: // ON THROW 
    if(p) free(p); 
    return NULL; 
} 

void foo_close(foo_t p) 
{ 
    if(p) fclose(p->ff); 
    free(p); 
} 

void foo_bar(foo_t p) 
{ 
    fprintf(p->ff,"%d\n",p->x+p->y); 
} 

int main() 
{ 
    foo_t f=foo_init(1); 
    if(!f) return 1; 
    foo_bar(f); 
    foo_close(f); 
    return 0; 
} 
+0

Dies ist nicht RAII. RAII bindet Ressourcen an Objekte auf dem Stapel und verwendet den Destruktor, um die Freigabe der Ressource zu handhaben. C hat keine Konstruktor/Destruktor-Semantik. Außerdem ist dieser Code ineffizienter als C++, da er auf Fehler prüft, während C++ sie als Ausnahme behandelt. – rlbond

+0

Es gibt zwei Punkte: a) C hat keine Ausnahmen, sie sind "wenn" Bedingungen. b) Bei RAII geht es nicht um Desructors, sondern um die richtige Initialisierung von Ressourcen und deren Freigabe. Es kann leicht in C über Stapel getan werden, wenn Sie den Code in 3 Teile teilen: Initialisierung, Arbeit und Finalisierung. – Artyom

1

Ein Weg I "verstecken" die Zuordnungsspeicher und Freigabe ist es, um benutzerdefinierte Behälter weg zu führen. Übergeben Sie dem Container ein nicht zugeordnetes Objekt. Lass es sich um malloc kümmern und wenn ich das Objekt entferne, lass es sich um das freie kümmern. Dies funktioniert natürlich nur, wenn Sie nur ein Objekt in einem Container speichern. Wenn ich Objektreferenzen haben alle über den Ort werde ich das Äquivalent und Destruktor Methoden mit c-Syntax erstellen:

glob* newGlob(); 
void freeGlob(glob* g); 

(von Objekt, das ich alles meinen Sie weisen darauf - nicht C++ Objekte).

7

Leider gibt es begrenzte Strategien zur Automatisierung der Speicherzuweisung und Freigabe in C. Der C++ - Compiler erzeugt viel Code hinter den Kulissen für Sie - er verfolgt jede Variable auf dem Stack und stellt sicher, dass der entsprechende Destruktor ist aufgerufen, wenn der Stapel aufgeräumt ist. Dies ist eine ziemlich anspruchsvolle Art der Codegenerierung, besonders wenn Sie Exceptions in den Mix werfen.

C ist auf der anderen Seite viel einfacher, weshalb es manchmal als "High-Level-Assemblersprache" bezeichnet wird. C hat keinen Mechanismus, um zu garantieren, dass ein bestimmter Codebeispiel aufgerufen wird, wenn eine Funktion beendet wird oder eine Variable aus dem Stapel entfernt wird. Es liegt also an Ihnen, jedes reservierte Speicherbit und jede Datei oder jedes Netzwerk zu verfolgen Steckdose öffnen Sie und reinigen Sie sie an der entsprechenden Stelle. Es gibt keine praktische Möglichkeit, einen automatischen Smart Pointer in C zu erstellen.

Ein Konzept, das Sie betrachten sollten, ist "Speicherpools". Im Grunde, anstatt zu versuchen, jeden einzelnen Speicherblock zu verfolgen, den Sie zuweisen, erstellen Sie einen Pool, erledigen einen Teil der Arbeit, platzieren jeden Speicherblock, den Sie zuweisen, und geben den gesamten Pool frei, wenn Sie fertig sind. Sie müssen hier ein wenig Leistung und Kontrolle hinnehmen, um die kognitive Belastung des Programmierers zu verringern, aber die meiste Zeit ist es das wert.

Sie sollten einen Blick auf das Apache Portable Runtime-Projekt werfen. Sie verfügen über eine Speicherpoolbibliothek (Dokumente unter http://apr.apache.org/docs/apr/1.3/group__apr__pools.html). Wenn APR zu viel für Sie ist, können Sie einen sehr einfachen Speicherpool mit drei Funktionen und einer verknüpften Listendatenstruktur implementieren. Pseudo-Code wäre so etwas wie:

struct Pool { 
    void* memoryBlock; 
    struct Pool *next; 
} 

struct Pool *createPool(void) { 
    /* allocate a Pool and return it */ 
} 

void addToPool(struct Pool *pool, void *memoryBlock) { 
    /* create a new Pool node and push it onto the list */ 
} 

void destroyPool(struct Pool *pool) { 
    /* walk the list, free each memory block then free its node */ 
} 

Mit dem Pool so etwas wie dieses:

int main(void) { 
    struct Pool *pool = createPool(); 
    /* pool is empty */ 

    doSomething(pool); 

    /* pool full of crap, clean it up and make a new one */ 
    destroyPool(pool); 
    pool = createPool(); 
    /* new pool is empty */ 

    doMoreStuff(pool); 
    destroyPool(pool); 

    return 0; 
} 
0

Ich bin nicht ganz sicher, was Sie fragen, aber C ist ziemlich einfach:

struct Foo *f0 = malloc(sizeof(*f)); // alloc uninitialized Foo struct 
struct Foo *f1 = calloc(1,sizeof(*f)); // alloc Foo struct cleared to all zeroes 

//You usually either want to clear your structs using calloc on allocation, or memset. If 
// you need a constructor, just write a function: 
Foo *Foo_Create(int a, char *b) 
{ 
    Foo *r = calloc(1,sizeof(*r)); 
    r->a = a; 
    r->b = strdup(b); 
    return r; 
} 

Here is a simple C workflow with arrays: 
struct Foo **foos = NULL; 
int n_foos = 0; 
... 
for(i = 0; i < n_foos; ++i) 
{ 
    struct Foo *f = calloc(1,sizeof(*f)); 
    foos = realloc(foos,sizeof(*foos)*++n_foos); // foos before and after may be different 
    foos[n_foos-1] = f; 
} 

Wenn Sie Lust bekommen, können Sie Makros schreiben helfen:

#define MALLOCP(P) calloc(1,sizeof(*P)) // calloc inits alloc'd mem to zero 

Ein paar Punkte:

  • malloc, calloc, realloc usw. alle frei() verwenden, um diese Dinge zu handhaben ist einfach. Sei einfach beständig.
  • Leistung für mallocs kann langsam sein. Jemand hat oben einen Link gepostet.Heutzutage sind schnelle Multi-Threaded-Zuweisungen der Schlüssel, siehe tcmalloc et al. Sie müssen sich wahrscheinlich nicht darum sorgen.
  • auf einem modernen virtuellen Speicherarchitektur, malloc wird fast nie scheitern, wenn Sie nicht aus virtuellen Adressraum sind. Wenn dies passiert, wechseln Sie zu 64bit;)
  • Stellen Sie sicher, dass Sie ein System verwenden, das Grenzen überprüft, freie Werte, Leak-Tracking und all diese guten Sachen (siehe Valgrind, Win32 Debug-Heap, etc.)
1

Es gibt viel, was Sie tun können, um Ihr Leben einfacher zu machen. Sie scheinen bereits auf die Idee gekommen zu sein, Fabriken/Konstruktoren für Ihre C-Objekte zu erstellen. Das ist ein guter Anfang.

Einige andere Ideen zu berücksichtigen.

  1. sich nicht mit dem Standard malloc/free zufrieden geben. Suchen Sie nach einem besseren, der opensourced wurde, oder schreiben Sie einen, der dem Speicherverbrauch der Objekte entspricht, die Sie erstellen. Wir reden hier auch von C, Sie werden Ihre Objekte mehr und mehr kostenlos überschreiben und vergessen, einige freizugeben, also bauen Sie Debugging-Unterstützung in Ihr malloc ein. Schreiben Sie Ihre eigenen ist nicht schwer, wenn Sie nicht eine finden, die Ihren Bedürfnissen entspricht.

  2. Verwenden Sie mehr als einen Heap. Verwenden Sie eine pro Klasse von Objekten, die Sie erstellen, verwenden Sie temporäre Heaps, wenn Sie wissen, dass Sie eine große Anzahl von verwandten transienten Objekten haben. Dadurch wird die Speicherfragmentierung verringert und Sie können den Speicher entsprechend der Verwendung verwalten.

  3. Blick auf Strategien wie Objective-C Becken

  4. wenn Sie denken, Sie verstehen, wie C++, dann das Hinzufügen Konstruktor Verhaltens Speicherzuweisung in einem Objekt Fabrik ist nicht so schwer zu tun, und mit einem speziell angefertigten kostenlos funktioniert können dann Sie die Möglichkeit bieten eine destructor auf das Objekt nennen free'd werden Sie wieder etwas von dem Verhalten ++ C geben Sie

    mochte
0

ich weiß, dass dies eine alte Post, aber es nicht wirklich viel gewesen eine umfassende Antwort auf Best Practice in Bezug auf Stil, was ich glaube, was die OP wirklich wollte, also hier ist meine Meinung zu Speicherzuteilung in C. Ich bin eher eine C++ - Person, so viele meiner Gedanken kommen von dieser Einstellung.

Es ist oft praktisch zu wissen, ob Ihr Zeiger zugewiesen ist, also weisen Sie einem Zeiger immer NULL zu, wenn Sie ihn deklarieren. Sie können auch eine sichere freie Funktion erstellen, die den Speicher freigibt und NULL zuordnet, damit Sie sich keine Sorgen machen müssen.

Wenn Sie Speicher in einer C-Datei reservieren, dann sollten Sie es in der gleichen Datei freigeben. Dies ist vielleicht restriktiver als benötigt, aber wenn Sie eine Bibliothek schreiben, sollten Sie definitiv jeden Speicher in Ihrer Bibliothek freigeben, der in Ihrer Bibliothek mallokiert ist. Der Grund dafür ist, dass Windows-DLLs einen anderen Heap als die EXE haben. Wenn Sie also Speicher in einer DLL mallozieren und in der EXE freigeben, verdirbt dies Ihren Heap.

Durch Erweiterung und aus Gründen der Symmetrie bedeutet dies, wenn Sie eine Funktion haben, die einen Zeiger auf zugewiesenen Speicher zurückgibt, dann sollten Sie eine Funktion haben, die diesen Speicher freigibt. Aus diesem Grund haben viele Librarys eine Initialisierungsfunktion, die einen Zeiger auf einige Daten zurückgibt (normalerweise als void *) und dann eine Aufräumfunktion, die die Ressourcen der Bibliothek freigibt. Wenn Sie malloc und frei innerhalb der gleichen Funktion können, dann ist das gut, da es Ihnen leicht macht, den Überblick zu behalten.

Versuchen Sie nicht, all Ihren Speicher am Anfang einer Funktion zu reservieren und dann am Ende freizugeben. Das bedeutet nur, dass wenn Sie einen Teil der Funktion zurückgeben wollen, müssen Sie den gesamten Speicher freigeben, während Sie, wenn Sie malloc und freien Speicher wie Sie gehen, weniger Zeiger auf frei haben werden.

Wenn Sie oft Funktionen haben, die viele Zeiger zuweisen, dann denken Sie darüber nach, ein Array zu erstellen, das am Anfang der Funktion Zeiger auf alle Ihre Zeiger hält und dann eine Funktion hat, die alle freigibt. Dies wird Ihnen das unvermeidliche "Ich werde zurückkommen und sortiere meine Speicherlecks später" -Syndrom ersparen, wenn Sie zur mittleren Funktion zurückkehren wollen.

Das Konzept der Fabriken ist nützlich. Eine Factory wäre eine Funktion, die den Speicher für eine Struktur malloksiert, der Struktur einen Funktionszeiger zuweist, ihre Variablen initialisiert und dann den Zeiger auf sie zurückgibt. Wenn der erste von ihnen ein Destruktor oder ein Array von bestimmten Funktionen war, dann können Sie eine generische Zerstörungsfunktion haben, die den Destruktor jeder Struktur aufrufen und dann den Speicher der Struktur freigeben kann. Sie können auch einige der internen Details der Klasse ausblenden, indem Sie die Struktur nach innen und nach außen zeigen. COM baut auf diesen Prinzipien auf.

Das sind nur die Möglichkeiten, wie ich Speicher in C anschaue. Es ist nicht so elegant wie in C++, aber da man sich auf Menschen verlässt, können Strategien wie die oben genannten die Dinge machen so einfach wie möglich für sie.

Beachten Sie auch, dass es immer Ausnahmen zu jeder Regel gibt - das sind nur Dinge, an die ich denke, wenn ich C benutze. Ich bin sicher, dass andere Leute andere Ideen haben.

Phil

Verwandte Themen