Ich habe immer gehört, dass man in C wirklich darauf achten muss, wie man den Speicher verwaltet. Und ich fange immer noch an, C zu lernen, aber bisher musste ich überhaupt keine Speicherverwaltungsaktivitäten durchführen. Ich stellte mir immer vor, Variablen freigeben und alle möglichen hässlichen Dinge tun zu müssen. Dies scheint jedoch nicht der Fall zu sein.
Kann mir jemand (mit Codebeispielen) ein Beispiel zeigen, wann Sie eine "Speicherverwaltung" durchführen müssten?
Antworten:
Es gibt zwei Stellen, an denen Variablen gespeichert werden können. Wenn Sie eine Variable wie folgt erstellen:
Die Variablen werden im " Stack " erstellt. Stapelvariablen werden automatisch freigegeben, wenn sie den Gültigkeitsbereich verlassen (dh wenn der Code sie nicht mehr erreichen kann). Sie könnten sie als "automatische" Variablen bezeichnen, aber das ist aus der Mode gekommen.
Viele Anfängerbeispiele verwenden nur Stapelvariablen.
Der Stapel ist schön, weil er automatisch ist, hat aber auch zwei Nachteile: (1) Der Compiler muss im Voraus wissen, wie groß die Variablen sind, und (b) der Stapelspeicher ist etwas begrenzt. Beispiel: In Windows ist der Stapel unter den Standardeinstellungen für den Microsoft-Linker auf 1 MB festgelegt, und nicht alles ist für Ihre Variablen verfügbar.
Wenn Sie zur Kompilierungszeit nicht wissen, wie groß Ihr Array ist, oder wenn Sie ein großes Array oder eine große Struktur benötigen, benötigen Sie "Plan B".
Plan B heißt " Haufen ". Normalerweise können Sie Variablen erstellen, die so groß sind, wie es das Betriebssystem zulässt, aber Sie müssen dies selbst tun. Frühere Beiträge haben Ihnen gezeigt, wie Sie dies tun können, obwohl es auch andere Möglichkeiten gibt:
(Beachten Sie, dass Variablen im Heap nicht direkt, sondern über Zeiger bearbeitet werden.)
Sobald Sie eine Heap-Variable erstellt haben, besteht das Problem darin, dass der Compiler nicht erkennen kann, wann Sie damit fertig sind, sodass Sie die automatische Freigabe verlieren. Hier kommt die "manuelle Freigabe" ins Spiel, auf die Sie sich bezogen haben. Ihr Code ist nun dafür verantwortlich, zu entscheiden, wann die Variable nicht mehr benötigt wird, und sie freizugeben, damit der Speicher für andere Zwecke verwendet werden kann. Für den obigen Fall mit:
Was diese zweite Option zu einem "bösen Geschäft" macht, ist, dass es nicht immer einfach ist zu wissen, wann die Variable nicht mehr benötigt wird. Wenn Sie vergessen, eine Variable freizugeben, wenn Sie sie nicht benötigen, belegt Ihr Programm mehr Speicher, als es benötigt. Diese Situation wird als "Leck" bezeichnet. Der "durchgesickerte" Speicher kann für nichts verwendet werden, bis Ihr Programm endet und das Betriebssystem alle seine Ressourcen wiederherstellt. Noch schlimmere Probleme sind möglich, wenn Sie versehentlich eine Heap-Variable freigeben, bevor Sie tatsächlich damit fertig sind.
In C und C ++ sind Sie dafür verantwortlich, Ihre Heap-Variablen wie oben gezeigt zu bereinigen. Es gibt jedoch Sprachen und Umgebungen wie Java- und .NET-Sprachen wie C #, die einen anderen Ansatz verwenden, bei dem der Heap selbst bereinigt wird. Diese zweite Methode, die als "Garbage Collection" bezeichnet wird, ist für den Entwickler viel einfacher, aber Sie zahlen eine Strafe für Overhead und Leistung. Es ist ein Gleichgewicht.
(Ich habe viele Details beschönigt, um eine einfachere, aber hoffentlich ebenere Antwort zu geben.)
quelle
malloc()
, seine Ursache UB,(char *)malloc(size);
siehe stackoverflow.com/questions/605845/…Hier ist ein Beispiel. Angenommen, Sie haben eine strdup () - Funktion, die eine Zeichenfolge dupliziert:
Und du nennst es so:
Sie können sehen, dass das Programm funktioniert, aber Sie haben Speicher (über malloc) zugewiesen, ohne ihn freizugeben. Sie haben Ihren Zeiger auf den ersten Speicherblock verloren, als Sie strdup das zweite Mal aufgerufen haben.
Dies ist keine große Sache für diese kleine Menge an Speicher, aber betrachten Sie den Fall:
Sie haben jetzt 11 Gig Arbeitsspeicher verbraucht (möglicherweise mehr, abhängig von Ihrem Speichermanager), und wenn Sie nicht abgestürzt sind, läuft Ihr Prozess wahrscheinlich ziemlich langsam.
Um dies zu beheben, müssen Sie free () für alles aufrufen, was mit malloc () erhalten wird, nachdem Sie es nicht mehr verwenden:
Hoffe dieses Beispiel hilft!
quelle
Sie müssen "Speicherverwaltung" durchführen, wenn Sie Speicher auf dem Heap anstelle des Stapels verwenden möchten. Wenn Sie nicht wissen, wie groß ein Array bis zur Laufzeit sein soll, müssen Sie den Heap verwenden. Beispielsweise möchten Sie möglicherweise etwas in einer Zeichenfolge speichern, wissen jedoch nicht, wie groß der Inhalt sein wird, bis das Programm ausgeführt wird. In diesem Fall würden Sie so etwas schreiben:
quelle
Ich denke, die prägnanteste Art, die Frage zu beantworten, um die Rolle des Zeigers in C zu betrachten. Der Zeiger ist ein leichter und dennoch leistungsstarker Mechanismus, der Ihnen immense Freiheit auf Kosten einer immensen Fähigkeit gibt, sich in den Fuß zu schießen.
In C liegt die Verantwortung dafür, dass Ihre Zeiger auf Ihr eigenes Gedächtnis verweisen, bei Ihnen und allein bei Ihnen. Dies erfordert einen organisierten und disziplinierten Ansatz, es sei denn, Sie verzichten auf Hinweise, was es schwierig macht, effektives C zu schreiben.
Die bisher veröffentlichten Antworten konzentrieren sich auf automatische (Stapel-) und Heap-Variablenzuweisungen. Die Verwendung der Stapelzuweisung sorgt zwar für automatisch verwalteten und bequemen Speicher, kann jedoch unter bestimmten Umständen (große Puffer, rekursive Algorithmen) zu dem schrecklichen Problem des Stapelüberlaufs führen. Es ist sehr systemabhängig, genau zu wissen, wie viel Speicher Sie auf dem Stapel zuweisen können. In einigen eingebetteten Szenarien können einige Dutzend Bytes Ihr Limit sein, in einigen Desktop-Szenarien können Sie Megabyte sicher verwenden.
Die Heap-Zuordnung ist der Sprache weniger eigen. Es handelt sich im Grunde genommen um eine Reihe von Bibliotheksaufrufen, die Ihnen den Besitz eines Speicherblocks bestimmter Größe gewähren, bis Sie bereit sind, ihn zurückzugeben ("frei"). Es klingt einfach, ist aber mit unermesslichem Kummer des Programmierers verbunden. Die Probleme sind einfach (zweimal oder gar nicht denselben Speicher freigeben [Speicherlecks], nicht genügend Speicher zuweisen [Pufferüberlauf] usw.), aber schwer zu vermeiden und zu debuggen. Ein hochdisziplinierter Ansatz ist in der Praxis absolut obligatorisch, aber die Sprache schreibt ihn natürlich nicht vor.
Ich möchte eine andere Art der Speicherzuweisung erwähnen, die von anderen Posts ignoriert wurde. Es ist möglich, Variablen statisch zuzuweisen, indem sie außerhalb einer Funktion deklariert werden. Ich denke, im Allgemeinen bekommt diese Art der Zuweisung einen schlechten Ruf, weil sie von globalen Variablen verwendet wird. Es gibt jedoch nichts, was besagt, dass der auf diese Weise zugewiesene Speicher nur als undisziplinierte globale Variable in einem Durcheinander von Spaghetti-Code verwendet werden kann. Die statische Zuweisungsmethode kann einfach verwendet werden, um einige der Fallstricke des Heaps und der automatischen Zuweisungsmethoden zu vermeiden. Einige C-Programmierer sind überrascht zu erfahren, dass große und hochentwickelte C-Embedded- und Spieleprogramme ohne Verwendung der Heap-Zuweisung erstellt wurden.
quelle
Hier gibt es einige gute Antworten zum Zuweisen und Freigeben von Speicher. Meiner Meinung nach besteht die schwierigere Seite der Verwendung von C darin, sicherzustellen, dass der einzige Speicher, den Sie verwenden, der von Ihnen zugewiesene Speicher ist - wenn dies nicht richtig gemacht wird, was Sie beenden up with ist der Cousin dieser Site - ein Pufferüberlauf - und Sie überschreiben möglicherweise den Speicher, der von einer anderen Anwendung verwendet wird, mit sehr unvorhersehbaren Ergebnissen.
Ein Beispiel:
Zu diesem Zeitpunkt haben Sie 5 Bytes für myString zugewiesen und mit "abcd \ 0" gefüllt (Zeichenfolgen enden mit null - \ 0). Wenn Ihre Zeichenfolgenzuordnung war
Sie würden "abcde" in den 5 Bytes zuweisen, die Sie Ihrem Programm zugewiesen haben, und das nachfolgende Nullzeichen würde am Ende davon stehen - ein Teil des Speichers, der nicht für Ihre Verwendung zugewiesen wurde und sein könnte kostenlos, kann aber auch von einer anderen Anwendung verwendet werden - Dies ist der kritische Teil der Speicherverwaltung, bei dem ein Fehler unvorhersehbare (und manchmal nicht wiederholbare) Folgen hat.
quelle
strcpy()
anstelle von zu verwenden=
. Ich nehme an, das war die Absicht von Chris BC.Eine Sache zu erinnern ist, immer Ihre Zeiger auf NULL initialisiert werden , da ein nicht initialisierte Zeiger eine Pseudo - Zufall gültige Speicheradresse enthalten, die Zeiger Fehler voran gehen lautlos machen. Indem Sie erzwingen, dass ein Zeiger mit NULL initialisiert wird, können Sie immer abfangen, ob Sie diesen Zeiger verwenden, ohne ihn zu initialisieren. Der Grund dafür ist, dass Betriebssysteme die virtuelle Adresse 0x00000000 mit allgemeinen Schutzausnahmen "verbinden", um die Verwendung von Nullzeigern abzufangen.
quelle
Möglicherweise möchten Sie auch die dynamische Speicherzuweisung verwenden, wenn Sie ein großes Array definieren müssen, z. B. int [10000]. Sie können es nicht einfach stapeln, denn dann, hm ... erhalten Sie einen Stapelüberlauf.
Ein weiteres gutes Beispiel wäre die Implementierung einer Datenstruktur, beispielsweise einer verknüpften Liste oder eines Binärbaums. Ich habe hier keinen Beispielcode zum Einfügen, aber Sie können ihn einfach googeln.
quelle
(Ich schreibe, weil ich der Meinung bin, dass die Antworten bisher nicht ganz zutreffen.)
Der Grund für die erwähnenswerte Speicherverwaltung ist, wenn Sie ein Problem / eine Lösung haben, bei der Sie komplexe Strukturen erstellen müssen. (Wenn Ihre Programme abstürzen, wenn Sie gleichzeitig zu viel Speicherplatz auf dem Stapel zuweisen, ist dies ein Fehler.) In der Regel ist die erste Datenstruktur, die Sie lernen müssen, eine Art Liste . Hier ist eine einzige verknüpfte, die mir auf den Kopf gestellt ist:
Natürlich möchten Sie ein paar andere Funktionen, aber im Grunde ist dies das, wofür Sie Speicherverwaltung benötigen. Ich sollte darauf hinweisen, dass es eine Reihe von Tricks gibt, die mit der "manuellen" Speicherverwaltung möglich sind, z.
Holen Sie sich einen guten Debugger ... Viel Glück!
quelle
@ Euro Micelli
Ein Nachteil ist, dass Zeiger auf den Stapel nicht mehr gültig sind, wenn die Funktion zurückgegeben wird. Sie können also keinen Zeiger von einer Funktion auf eine Stapelvariable zurückgeben. Dies ist ein häufiger Fehler und ein Hauptgrund, warum Sie nicht nur mit Stapelvariablen auskommen können. Wenn Ihre Funktion einen Zeiger zurückgeben muss, müssen Sie sich mit der Speicherverwaltung befassen.
quelle
Sie haben natürlich Recht. Ich glaube, das war schon immer so, obwohl ich keine Kopie von K & R zu überprüfen habe.
Ich mag nicht viele der impliziten Konvertierungen in C, daher verwende ich eher Casts, um "Magie" sichtbarer zu machen. Manchmal hilft es der Lesbarkeit, manchmal nicht, und manchmal wird ein stiller Fehler vom Compiler abgefangen. Trotzdem habe ich auf die eine oder andere Weise keine feste Meinung dazu.
Ja ... du hast mich dort erwischt. Ich verbringe viel mehr Zeit in C ++ als in C. Vielen Dank, dass Sie das bemerkt haben.
quelle
In C haben Sie tatsächlich zwei verschiedene Möglichkeiten. Zum einen können Sie das System den Speicher für Sie verwalten lassen. Alternativ können Sie dies auch selbst tun. Im Allgemeinen möchten Sie so lange wie möglich bei ersteren bleiben. Der automatisch verwaltete Speicher in C ist jedoch äußerst begrenzt, und Sie müssen den Speicher in vielen Fällen manuell verwalten, z.
ein. Sie möchten, dass die Variable die Funktionen überlebt, und Sie möchten keine globale Variable haben. Ex:
b. Sie möchten dynamisch zugewiesenen Speicher haben. Das häufigste Beispiel ist ein Array ohne feste Länge:
Ein langer Wert reicht aus, um ALLES zu halten. Denken Sie daran, es zu befreien, sonst werden Sie es bereuen. Dies ist einer meiner Lieblingstricks, um Spaß in C: D zu haben.
Im Allgemeinen möchten Sie sich jedoch von Ihren Lieblingstricks (T___T) fernhalten. Sie werden Ihr Betriebssystem früher oder später beschädigen, wenn Sie sie zu oft verwenden. Solange Sie * alloc und free nicht verwenden, können Sie mit Sicherheit sagen, dass Sie noch jungfräulich sind und der Code immer noch gut aussieht.
quelle
Sicher. Wenn Sie ein Objekt erstellen, das außerhalb des Bereichs existiert, in dem Sie es verwenden. Hier ist ein erfundenes Beispiel (denken Sie daran, dass meine Syntax deaktiviert ist; mein C ist verrostet, aber dieses Beispiel veranschaulicht das Konzept immer noch):
In diesem Beispiel verwende ich während der Lebensdauer von MyClass ein Objekt vom Typ SomeOtherClass. Das SomeOtherClass-Objekt wird in mehreren Funktionen verwendet, daher habe ich den Speicher dynamisch zugewiesen: Das SomeOtherClass-Objekt wird beim Erstellen von MyClass erstellt, während der Lebensdauer des Objekts mehrmals verwendet und nach dem Freigeben von MyClass freigegeben.
Wenn dies echter Code wäre, gäbe es natürlich keinen Grund (abgesehen vom möglichen Stapelspeicherverbrauch), myObject auf diese Weise zu erstellen, aber diese Art der Objekterstellung / -zerstörung wird nützlich, wenn Sie viele Objekte haben und genau steuern möchten Wenn sie erstellt und zerstört werden (damit Ihre Anwendung beispielsweise nicht 1 GB RAM für die gesamte Lebensdauer verbraucht) und in einer Windows-Umgebung, ist dies als von Ihnen erstellte Objekte (z. B. Schaltflächen) ziemlich obligatorisch. müssen weit außerhalb des Anwendungsbereichs einer bestimmten Funktion (oder sogar einer Klasse) existieren.
quelle