Wie verwende ich valgrind, um Speicherlecks zu finden?

Lesezeit: 12 Minuten

Benutzeravatar von user484457
Benutzer484457

Wie verwende ich valgrind, um die Speicherlecks in einem Programm zu finden?

Bitte hilft mir jemand und beschreibt die Schritte zur Durchführung des Verfahrens?

Ich verwende Ubuntu 10.04 und habe ein Programm a.cbitte hilf mir.

  • Sie verwenden Valgrind, um Ihre zu testen zusammengestellt Programm, nicht der Quellcode.

    – Toni

    15. März 2011 um 12:54 Uhr

  • Die Antwort von @RageD unten ist richtig, warum akzeptierst du sie nicht?

    – Pratik Singhal

    13. Februar 2014 um 5:04 Uhr

  • Ein Leck wird durch etwas verursacht, das Sie verursacht haben scheitern zu tun – dh. freier zugewiesener Speicher. Daher kann Valgrind Ihnen nicht zeigen, “wo” das Leck ist – nur Sie wissen, wo der zugewiesene Speicher nicht mehr benötigt wird. Indem Sie Ihnen jedoch mitteilen, welche Zuordnung nicht freigegeben wird, und indem Sie die Verwendung dieses Speichers durch Ihr Programm verfolgen, sollten Sie in der Lage sein, festzustellen, wo er freigegeben werden soll. Ein häufiger Fehler besteht darin, eine Funktion mit einem Fehler zu verlassen, ohne den zugewiesenen Speicher freizugeben.

    – MikeW

    13. Januar 2016 um 15:57 Uhr


  • Verwandte: mit jedem Tool: stackoverflow.com/questions/6261201/…

    – Ciro Santilli OurBigBook.com

    10. September 2019 um 19:52 Uhr

So führen Sie Valgrind aus

Nicht um das OP zu beleidigen, aber für diejenigen, die zu dieser Frage kommen und noch neu bei Linux sind –Möglicherweise müssen Sie Valgrind installieren auf Ihrem System.

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.
sudo pacman -Syu valgrind  # Arch, Manjaro, Garuda, etc

Valgrind ist ohne Weiteres für C/C++-Code verwendbar, kann aber bei richtiger Konfiguration sogar für andere Sprachen verwendet werden (siehe hier für Python).

Um Valgrind zu führenübergeben Sie die ausführbare Datei als Argument (zusammen mit allen Parametern an das Programm).

valgrind --leak-check=full \
         --show-leak-kinds=all \
         --track-origins=yes \
         --verbose \
         --log-file=valgrind-out.txt \
         ./executable exampleParam1

Die Flaggen sind kurz:

  • --leak-check=full: “jedes einzelne Leck wird detailliert angezeigt”
  • --show-leak-kinds=all: Zeigen Sie alle “bestimmten, indirekten, möglichen, erreichbaren” Leckarten im “vollständigen” Bericht an.
  • --track-origins=yes: Nutzleistung der Geschwindigkeit vorziehen. Dies verfolgt die Ursprünge nicht initialisierter Werte, was bei Speicherfehlern sehr nützlich sein kann. Erwägen Sie, Valgrind auszuschalten, wenn es unannehmbar langsam ist.
  • --verbose: Kann Sie über ungewöhnliches Verhalten Ihres Programms informieren. Wiederholen Sie dies für mehr Ausführlichkeit.
  • --log-file: In eine Datei schreiben. Nützlich, wenn die Ausgabe den Terminalbereich überschreitet.

Schließlich möchten Sie einen Valgrind-Bericht sehen, der so aussieht:

HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
  total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated
 
All heap blocks were freed -- no leaks are possible
 
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

Ich habe ein Leck, aber WO?

Sie haben also ein Speicherleck und Valgrind sagt nichts Sinnvolles. Vielleicht so etwas:

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (in /home/Peri461/Documents/executable)

Werfen wir einen Blick auf den C-Code, den ich auch geschrieben habe:

#include <stdlib.h>

int main() {
    char* string = malloc(5 * sizeof(char)); //LEAK: not freed!
    return 0;
}

Nun, es gingen 5 Bytes verloren. Wie ist es passiert? Der Fehlerbericht sagt nur
main und malloc. In einem größeren Programm wäre es sehr mühsam, das aufzuspüren. Dies liegt daran, wie die ausführbare Datei kompiliert wurde. Wir können tatsächlich zeilenweise Details darüber erhalten, was schief gelaufen ist. Kompilieren Sie Ihr Programm mit einem Debug-Flag neu (ich verwende gcc hier):

gcc -o executable -std=c11 -Wall main.c         # suppose it was this at first
gcc -o executable -std=c11 -Wall -ggdb3 main.c  # add -ggdb3 to it

Jetzt mit diesem Debug-Build, Valgrind zeigt auf die genaue Codezeile
Zuweisen des Speichers, der durchgesickert ist! (Der Wortlaut ist wichtig: Es ist möglicherweise nicht genau dort, wo Ihr Leck ist, aber was wurde durchgesickert. Die Spur hilft beim Finden
wo.)

5 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x40053E: main (main.c:4)

Techniken zum Debuggen von Speicherlecks und Fehlern

  • Gebrauch machen von www.cplusplus.com! Es hat eine großartige Dokumentation zu C/C++-Funktionen.

  • Allgemeine Hinweise für Speicherlecks:

  • Stellen Sie sicher, dass Ihr dynamisch zugewiesener Speicher tatsächlich freigegeben wird.

  • Weisen Sie keinen Speicher zu und vergessen Sie, den Zeiger zuzuweisen.

  • Überschreiben Sie einen Zeiger nicht mit einem neuen, es sei denn, der alte Speicher wird freigegeben.

  • Allgemeine Hinweise bei Speicherfehlern:

  • Greifen Sie auf Adressen und Indizes zu und schreiben Sie an diese, von denen Sie sicher sind, dass sie Ihnen gehören. Speicherfehler unterscheiden sich von Lecks; sie sind oft gerecht IndexOutOfBoundsException
    Typ Probleme.

  • Greifen Sie nicht auf den Speicher zu und schreiben Sie nicht in den Speicher, nachdem Sie ihn freigegeben haben.

  • Manchmal können Ihre Leaks/Fehler miteinander verknüpft werden, ähnlich wie eine IDE, die entdeckt, dass Sie noch keine schließende Klammer eingegeben haben. Die Lösung eines Problems kann andere lösen, also suchen Sie nach einem, der gut aussieht, und wenden Sie einige dieser Ideen an:

  • Listen Sie die Funktionen in Ihrem Code auf, die von dem “anstößigen” Code abhängen/abhängig sind, der den Speicherfehler aufweist. Verfolgen Sie die Ausführung des Programms (vielleicht sogar in gdb vielleicht) und suchen Sie nach Vorbedingungs-/Nachbedingungsfehlern. Die Idee ist, die Ausführung Ihres Programms zu verfolgen und sich dabei auf die Lebensdauer des zugewiesenen Speichers zu konzentrieren.

  • Versuchen Sie, den “anstößigen” Codeblock auszukommentieren (in angemessenem Rahmen, damit Ihr Code immer noch kompiliert wird). Wenn der Valgrind-Fehler verschwindet, haben Sie herausgefunden, wo er ist.

  • Wenn alles andere fehlschlägt, versuchen Sie, es nachzuschlagen. Valgrind hat Dokumentation zu!


Ein Blick auf häufige Lecks und Fehler

Achten Sie auf Ihre Hinweise

60 bytes in 1 blocks are definitely lost in loss record 1 of 1
   at 0x4C2BB78: realloc (vg_replace_malloc.c:785)
   by 0x4005E4: resizeArray (main.c:12)
   by 0x40062E: main (main.c:19)

Und der Code:

#include <stdlib.h>
#include <stdint.h>

struct _List {
    int32_t* data;
    int32_t length;
};
typedef struct _List List;

List* resizeArray(List* array) {
    int32_t* dPtr = array->data;
    dPtr = realloc(dPtr, 15 * sizeof(int32_t)); //doesn't update array->data
    return array;
}

int main() {
    List* array = calloc(1, sizeof(List));
    array->data = calloc(10, sizeof(int32_t));
    array = resizeArray(array);

    free(array->data);
    free(array);
    return 0;
}

Als Lehrassistent habe ich diesen Fehler oft gesehen. Der Schüler verwendet eine lokale Variable und vergisst, den ursprünglichen Zeiger zu aktualisieren. Der Fehler hier ist, das zu bemerken realloc kann den zugewiesenen Speicher tatsächlich an eine andere Stelle verschieben und die Position des Zeigers ändern. Wir gehen dann resizeArray ohne es zu sagen
array->data wohin das Array verschoben wurde.

Ungültiger Schreibvorgang

1 errors in context 1 of 1:
Invalid write of size 1
   at 0x4005CA: main (main.c:10)
 Address 0x51f905a is 0 bytes after a block of size 26 alloc'd
   at 0x4C2B975: calloc (vg_replace_malloc.c:711)
   by 0x400593: main (main.c:5)

Und der Code:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* alphabet = calloc(26, sizeof(char));

    for(uint8_t i = 0; i < 26; i++) {
        *(alphabet + i) = 'A' + i;
    }
    *(alphabet + 26) = '\0'; //null-terminate the string?

    free(alphabet);
    return 0;
}

Beachten Sie, dass Valgrind uns auf die kommentierte Codezeile oben verweist. Das Array der Größe 26 wird indiziert [0,25] weshalb *(alphabet + 26) ist ein ungültiger Schreibvorgang – er ist außerhalb der Grenzen. Ein ungültiger Schreibvorgang ist ein häufiges Ergebnis von Off-by-One-Fehlern. Sehen Sie sich die linke Seite Ihrer Zuweisungsoperation an.

Ungültiger Lesevorgang

1 errors in context 1 of 1:
Invalid read of size 1
   at 0x400602: main (main.c:9)
 Address 0x51f90ba is 0 bytes after a block of size 26 alloc'd
   at 0x4C29BE3: malloc (vg_replace_malloc.c:299)
   by 0x4005E1: main (main.c:6)

Und der Code:

#include <stdlib.h>
#include <stdint.h>

int main() {
    char* destination = calloc(27, sizeof(char));
    char* source = malloc(26 * sizeof(char));

    for(uint8_t i = 0; i < 27; i++) {
        *(destination + i) = *(source + i); //Look at the last iteration.
    }

    free(destination);
    free(source);
    return 0;
}

Valgrind weist uns auf die kommentierte Zeile oben hin. Schauen Sie sich die letzte Iteration hier an, die ist
*(destination + 26) = *(source + 26);. Jedoch, *(source + 26) ist wieder außerhalb der Grenzen, ähnlich wie beim ungültigen Schreiben. Ungültige Lesevorgänge sind auch ein häufiges Ergebnis von Off-by-One-Fehlern. Sehen Sie sich die rechte Seite Ihrer Zuweisungsoperation an.


Die Open Source (U/Dys)topia

Woher weiß ich, wann das Leck mir gehört? Wie finde ich mein Leak, wenn ich den Code einer anderen Person verwende? Ich habe ein Leck gefunden, das nicht mir gehört; sollte ich etwas tun? Alles berechtigte Fragen. Zuerst 2 Beispiele aus der realen Welt, die 2 Arten von gemeinsamen Begegnungen zeigen.

Janson: eine JSON-Bibliothek

#include <jansson.h>
#include <stdio.h>

int main() {
    char* string = "{ \"key\": \"value\" }";

    json_error_t error;
    json_t* root = json_loads(string, 0, &error); //obtaining a pointer
    json_t* value = json_object_get(root, "key"); //obtaining a pointer
    printf("\"%s\" is the value field.\n", json_string_value(value)); //use value

    json_decref(value); //Do I free this pointer?
    json_decref(root);  //What about this one? Does the order matter?
    return 0;
}

Dies ist ein einfaches Programm: Es liest einen JSON-String und analysiert ihn. In der Entwicklung verwenden wir Bibliotheksaufrufe, um das Parsing für uns zu erledigen. Jansson nimmt die notwendigen Zuordnungen dynamisch vor, da JSON verschachtelte Strukturen von sich selbst enthalten kann. Damit sind jedoch nicht wir gemeint decref oder den uns gegebenen Speicher von jeder Funktion “befreien”. Tatsächlich wirft dieser Code, den ich oben geschrieben habe, sowohl ein “Ungültiges Lesen” als auch ein “Ungültiges Schreiben”. Diese Fehler verschwinden, wenn Sie die entfernen decref Linie für value.

Wieso den? Die Variable value wird in der Jansson-API als “geliehene Referenz” betrachtet. Jansson behält seinen Speicher für Sie im Auge, und Sie müssen es einfach tun decref
JSON-Strukturen unabhängig voneinander. Die Lektion hier:
lesen Sie die Dokumentation. Wirklich. Es ist manchmal schwer zu verstehen, aber sie sagen Ihnen, warum diese Dinge passieren. Stattdessen haben wir bestehende Fragen zu diesem Speicherfehler.

SDL: eine Grafik- und Spielbibliothek

#include "SDL2/SDL.h"

int main(int argc, char* argv[]) {
    if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) != 0) {
        SDL_Log("Unable to initialize SDL: %s", SDL_GetError());
        return 1;
    }

    SDL_Quit();
    return 0;
}

Was ist falsch mit dieser Code? Es leckt ständig ~ 212 KiB Speicher für mich. Nehmen Sie sich einen Moment Zeit, um darüber nachzudenken. Wir schalten SDL ein und wieder aus. Antworten? Da ist nichts falsch.

Das mag zunächst bizarr klingen. Um ehrlich zu sein, die Grafiken sind chaotisch und manchmal müssen Sie einige Lecks als Teil der Standardbibliothek akzeptieren. Die Lektion hier: Sie müssen nicht jedes Speicherleck unterdrücken. Manchmal muss man einfach die Lecks unterdrücken weil es sich um bekannte Probleme handelt, gegen die Sie nichts tun können. (Dies ist nicht meine Erlaubnis, Ihre eigenen Leaks zu ignorieren!)

Antworten auf die Leere

Woher weiß ich, wann das Leck mir gehört?
Es ist. (jedenfalls zu 99% sicher)

Wie finde ich mein Leak, wenn ich den Code einer anderen Person verwende?
Die Chancen stehen gut, dass jemand anderes es bereits gefunden hat. Versuchen Sie Google! Wenn das fehlschlägt, verwenden Sie die Fähigkeiten, die ich Ihnen oben gegeben habe. Wenn dies fehlschlägt und Sie hauptsächlich API-Aufrufe und wenig von Ihrem eigenen Stack-Trace sehen, lesen Sie die nächste Frage.

Ich habe ein Leck gefunden, das nicht mir gehört; sollte ich etwas tun?
Ja! Die meisten APIs bieten Möglichkeiten zum Melden von Fehlern und Problemen. Benutze sie! Helfen Sie mit, den Tools, die Sie in Ihrem Projekt verwenden, etwas zurückzugeben!


Weiterführende Lektüre

Danke, dass du so lange bei mir geblieben bist. Ich hoffe, Sie haben etwas gelernt, da ich versucht habe, mich an das breite Spektrum von Menschen zu halten, die zu dieser Antwort gekommen sind. Einige Dinge, die Sie hoffentlich schon gefragt haben: Wie funktioniert der Speicherzuordner von C? Was ist eigentlich ein Speicherleck und ein Speicherfehler? Wie unterscheiden sie sich von Segfaults? Wie funktioniert Valgrind? Wenn Sie eines davon hatten, nähren Sie bitte Ihre Neugier:

  • Weitaus bessere Antwort, schade, dass dies nicht die akzeptierte Antwort ist.

    – A. Smoljak

    25. März 2018 um 10:56 Uhr

  • Ich glaube, es ist eine gute Praxis, so etwas zu tun, ich habe selbst ein paar gemacht

    – A. Smoljak

    27. März 2018 um 6:39 Uhr

  • Kann ich diese Antwort markieren und als zukünftige Referenz für mich selbst verwenden? Gute Arbeit!

    – Zappen

    21. Oktober 2018 um 21:25 Uhr

  • tut memcheck Tool ist standardmäßig aktiviert?

    – abhiarora

    3. April 2020 um 17:23 Uhr

  • @abhiarora Ja. Das sagt uns die Manpage memcheck ist das Standardwerkzeug: --tool=<toolname> [default: memcheck]

    Benutzer7851115

    7. Mai 2020 um 17:46 Uhr

Benutzeravatar von RageD
WutD

Versuche dies:

valgrind --leak-check=full -v ./your_program

Solange valgrind installiert ist, wird es Ihr Programm durchlaufen und Ihnen mitteilen, was falsch ist. Es kann Ihnen Hinweise und ungefähre Orte geben, an denen Ihre Lecks gefunden werden können. Wenn Sie segfault’ing sind, versuchen Sie, es durchzulaufen gdb.

  • Was bedeutet “Ihr_Programm”? Ist dieser Quellcode-Speicherort oder Anwendungsname wie eine APK-Datei?

    – Bulma

    29. Juni 2016 um 7:16 Uhr


  • your_program == der Name der ausführbaren Datei oder ein beliebiger Befehl, den Sie zum Ausführen Ihrer Anwendung verwenden.

    – Wut D

    30. Juni 2016 um 15:47 Uhr

Benutzeravatar von Rajat Paliwal
Rajat Paliwal

Du kannst rennen:

valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]

Sie können einen Alias ​​in der .bashrc-Datei wie folgt erstellen

alias vg='valgrind --leak-check=full -v --track-origins=yes --log-file=vg_logfile.out'

Wenn Sie also Speicherlecks überprüfen möchten, tun Sie es einfach

vg ./<name of your executable> <command line parameters to your executable>

Dadurch wird eine Valgrind-Protokolldatei im aktuellen Verzeichnis generiert.

1427400cookie-checkWie verwende ich valgrind, um Speicherlecks zu finden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy