So verstehen Sie den Speicher Ihres Programms

Wenn Sie in einer Sprache wie C oder C ++ codieren, können Sie auf niedrigerer Ebene mit Ihrem Speicher interagieren. Manchmal entstehen dadurch viele Probleme, die Sie vorher nicht hatten: Segfaults . Diese Fehler sind ziemlich ärgerlich und können Ihnen große Probleme bereiten. Dies sind häufig Indikatoren dafür, dass Sie Speicher verwenden, den Sie nicht verwenden sollten.

Eines der häufigsten Probleme ist der Zugriff auf bereits freigegebenen Speicher. Dies ist Speicher, mit dem Sie entweder freigegeben haben free, oder Speicher, den Ihr Programm automatisch freigegeben hat, beispielsweise vom Stapel.

Das alles zu verstehen ist wirklich einfach und wird Sie definitiv dazu bringen, besser und intelligenter zu programmieren.

Wie ist die Erinnerung aufgeteilt?

Der Speicher ist in mehrere Segmente unterteilt. Zwei der wichtigsten für diesen Beitrag sind der Stapel und der Haufen . Der Stapel ist eine geordnete Einfügestelle, während der Heap nur zufällig ist - Sie weisen Speicher zu, wo immer Sie können.

Der Stapelspeicher verfügt über eine Reihe von Möglichkeiten und Operationen für seine Arbeit. Hier werden einige Registerinformationen Ihres Prozessors gespeichert. Und hier finden Sie relevante Informationen zu Ihrem Programm - welche Funktionen aufgerufen werden, welche Variablen Sie erstellt haben und weitere Informationen. Dieser Speicher wird auch vom Programm und nicht vom Entwickler verwaltet.

Der Heap wird häufig verwendet, um große Speichermengen zuzuweisen, die so lange vorhanden sein sollen, wie der Entwickler dies wünscht. Das sei gesagt, es ist die Aufgabe der Entwickler die Nutzung des Speichers auf dem Heap zu steuern . Wenn Sie komplexe Programme erstellen, müssen Sie häufig große Speicherblöcke zuweisen, und dort verwenden Sie den Heap. Wir nennen das Dynamic Memory .

Sie legen jedes Mal Dinge auf den Haufen, wenn Sie mallocSpeicher für etwas zuweisen. Jeder andere Aufruf, der so verläuft, int i;ist Stapelspeicher. Dies zu wissen ist wirklich wichtig, damit Sie Fehler in Ihrem Programm leicht finden und Ihre Segfault-Fehlersuche weiter verbessern können.

Den Stapel verstehen

Obwohl Sie möglicherweise nichts davon wissen, weist Ihr Programm ständig Stapelspeicher zu, damit es funktioniert. Jede lokale Variable und jede Funktion, die Sie aufrufen, geht dorthin. Auf diese Weise können Sie viele Dinge tun - die meisten davon sind Dinge, die Sie nicht wollten - wie Pufferüberläufe und Zugriff auf falschen Speicher.

Wie funktioniert es wirklich?

Der Stapel ist eine LIFO-Datenstruktur (Last-In-First-Out). Sie können es als eine Schachtel mit perfekt angepassten Büchern betrachten - das letzte Buch, das Sie platzieren, ist das erste, das Sie herausnehmen. Mithilfe dieser Struktur kann das Programm alle seine Vorgänge und Bereiche mithilfe von zwei einfachen Vorgängen einfach verwalten: Push und Pop .

Diese beiden machen genau das Gegenteil voneinander. Push fügt den Wert oben im Stapel ein. Pop nimmt den höchsten Wert davon.

Um den aktuellen Speicherplatz zu verfolgen, gibt es ein spezielles Prozessorregister namens Stack Pointer . Jedes Mal, wenn Sie etwas speichern müssen - wie eine Variable oder die Rücksprungadresse einer Funktion - wird der Stapelzeiger nach oben gedrückt und verschoben. Jedes Mal, wenn Sie eine Funktion verlassen, wird alles vom Stapelzeiger bis zur gespeicherten Rücksprungadresse der Funktion eingeblendet. Es ist einfach!

Um zu testen, ob Sie verstanden haben, verwenden wir das folgende Beispiel (versuchen Sie, den Fehler allein zu finden ☺️):

Wenn Sie es ausführen, wird das Programm einfach segfault. Warum passiert das? Alles sieht gut aus! Außer über ... den Stapel.

Wenn wir die Funktion aufrufen createArray, wird der Stapel:

  • speichert die Absenderadresse,
  • erstellt arrim Stapelspeicher und gibt ihn zurück (ein Array ist einfach ein Zeiger auf einen Speicherort mit seinen Informationen)
  • aber da wir es nicht benutzt haben, wird malloces im Stapelspeicher gespeichert.

Nachdem wir den Zeiger zurückgegeben haben, nimmt das Programm die Informationen aus dem Stapel und verwendet sie nach Bedarf, da wir keine Kontrolle über Stapeloperationen haben. Wenn wir versuchen, das Array zu füllen, nachdem wir von der Funktion zurückgekehrt sind, beschädigen wir den Speicher, wodurch das Programm fehlerhaft wird.

Den Haufen verstehen

Im Gegensatz zum Stapel wird der Heap verwendet, wenn etwas unabhängig von Funktionen und Bereichen für einige Zeit vorhanden sein soll. Um diesen Speicher zu nutzen, ist die C-Sprache stdlib wirklich gut, da sie zwei großartige Funktionen bietet : mallocund free.

Malloc (Speicherzuordnung) fordert das System nach der angeforderten Speichermenge an und gibt einen Zeiger auf die Startadresse zurück. Free teilt dem System mit, dass der von uns angeforderte Speicher nicht mehr benötigt wird und für andere Aufgaben verwendet werden kann. Sieht ganz einfach aus - solange Sie Fehler vermeiden.

Das System kann nicht überschreiben, was Entwickler verlangt haben. Es hängt also von uns Menschen ab, es mit den beiden oben genannten Funktionen zu verwalten. Dies öffnet die Tür für einen menschlichen Fehler: Speicherlecks.

Speicherverlust ist Speicher, der vom Benutzer angefordert wurde und der nie freigegeben wurde - wenn das Programm beendet wurde oder Zeiger auf seine Speicherorte verloren gingen. Dadurch verbraucht das Programm viel mehr Speicher als vorgesehen. Um dies zu vermeiden, geben wir es jedes Mal frei, wenn wir kein Heap-zugewiesenes Element mehr benötigen.

Im obigen Bild gibt der schlechte Weg niemals den von uns verwendeten Speicher frei. Dies führt dazu, dass 20 * 4 Bytes (int-Größe in 64-Bit) = 80 Bytes verschwendet werden. Das sieht vielleicht nicht so gut aus, aber stellen Sie sich vor, Sie tun dies nicht in einem riesigen Programm. Wir können am Ende Gigabyte verschwenden!

Die Verwaltung Ihres Heapspeichers ist wichtig, um den Speicher Ihres Programms effizient zu gestalten. Sie müssen aber auch vorsichtig sein, wie Sie es verwenden. Genau wie im Stapelspeicher kann es nach dem Freigeben des Speichers zu einem Segfault kommen, wenn Sie darauf zugreifen oder ihn verwenden.

Bonus: Strukturen und der Haufen

Einer der häufigsten Fehler bei der Verwendung von Strukturen besteht darin, die Struktur einfach freizugeben. Dies ist in Ordnung, solange wir Zeigern innerhalb der Struktur keinen Speicher zugewiesen haben. Wenn Zeigern innerhalb der Struktur Speicher zugewiesen wird, müssen sie zuerst freigegeben werden. Dann können wir die gesamte Struktur freigeben.

Wie ich meine Speicherverlustprobleme löse

Die meiste Zeit, wenn ich in C programmiere, verwende ich Strukturen. Daher habe ich immer zwei obligatorische Funktionen für meine Strukturen: den Konstruktor und den Destruktor .

Diese beiden Funktionen sind die einzigen, bei denen ich Mallocs und Frees für die Struktur verwende. Dies macht es wirklich einfach und leicht, meine Speicherlecks zu lösen.

(Wenn Sie mehr darüber erfahren möchten, wie Sie das Lesen von Code vereinfachen können, lesen Sie meinen Beitrag zur Abstraktion.)

Ein großartiges Tool zur Speicherverwaltung - Valgrind

Es ist schwierig, Ihr Gedächtnis zu verwalten und sicherzustellen, dass Sie alles richtig gehandhabt haben. Ein großartiges Tool, um zu überprüfen, ob sich Ihr Programm korrekt verhält, ist Valgrind. Dieses Tool validiert Ihr Programm und teilt Ihnen mit, wie viel Speicher Sie zugewiesen haben, wie viel freigegeben wurde, wenn Sie versucht haben, in einen falschen Speicherbereich zu schreiben. Mit diesem Tool können Sie überprüfen, ob alles in Ordnung ist, und Sie sollten es verwenden, um dies zu vermeiden Sicherheitskompromisse.

Vergiss nicht mir zu folgen!

Neben dem Posten hier auf Medium bin ich auch auf Twitter.

Wenn Sie Fragen oder Anregungen haben, zögern Sie nicht, mich zu kontaktieren.