Bellek sızıntılarını bulmak için valgrind'i nasıl kullanabilirim?


183

Bir programdaki bellek sızıntılarını bulmak için valgrind'i nasıl kullanabilirim?

Lütfen biri bana yardım etmeli ve prosedürü uygulamaya yönelik adımları açıklamalıdır?

Ubuntu 10.04 kullanıyorum ve bir programım var a.c, lütfen bana yardım et.


16
Kaynak kodunu değil, derlenmiş programınızı test etmek için valgrind kullanırsınız .
Tony

6
@RageD tarafından verilen cevap doğrudur, neden kabul etmiyorsunuz?
Pratik Singhal

1
Bir sızıntı Eğer bir şeyin neden olup başarısız yani - yapmak. boş bellek. Bu nedenle Valgrind size sızıntının nerede olduğunu gösteremez - sadece ayrılan belleğin artık gerekli olmadığını bilirsiniz. Bununla birlikte, hangi tahsisin serbest olmadığını () d söyleyerek, bu belleğin programınız boyunca kullanımını izleyerek, nereden boşaltılacağını belirleyebilmeniz gerekir () d. Yaygın bir hata, ayrılmış belleği boşaltmadan bir işlevden çıkma hatasıdır.
MikeW

Yanıtlar:


298

Valgrind Nasıl Çalışır

OP'ye hakaret etmek için değil, bu soruya gelen ve hala Linux için yeni olanlar için - Valgrind'i sisteminize yüklemeniz gerekebilir .

sudo apt install valgrind  # Ubuntu, Debian, etc.
sudo yum install valgrind  # RHEL, CentOS, Fedora, etc.

Valgrind C / C ++ kodu için kolaylıkla kullanılabilir, ancak (bkz doğru yapılandırıldığında, hatta diğer diller için kullanılabilir , bu Python için).

Valgrind'i çalıştırmak için , yürütülebilir dosyayı bağımsız değişken olarak (programa parametrelerle birlikte) iletin .

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

Bayraklar kısaca:

  • --leak-check=full: "her bir sızıntı ayrıntılı olarak gösterilecektir"
  • --show-leak-kinds=all: "Tam" raporunda "kesin, dolaylı, olası, ulaşılabilir" tüm sızıntı türlerini göster.
  • --track-origins=yes: Hız üzerinden faydalı çıktıyı tercih edin. Bu, bellek hataları için çok yararlı olabilecek başlatılmamış değerlerin kökenlerini izler. Valgrind kabul edilemez derecede yavaşsa kapatmayı düşünün.
  • --verbose: Programınızın alışılmadık davranışlarından bahsedebilir. Daha fazla ayrıntı için tekrarlayın.
  • --log-file: Bir dosyaya yazın. Çıkış terminal alanını aştığında kullanışlıdır.

Son olarak, şuna benzeyen bir Valgrind raporu görmek istersiniz:

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)

Sızıntı var ama NEREDE ?

Yani, bir bellek sızıntınız var ve Valgrind anlamlı bir şey söylemiyor. Belki de böyle bir şey:

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)

Ben de yazdığım C koduna bir göz atalım:

#include <stdlib.h>

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

5 bayt kayboldu. Nasıl oldu? Hata raporu sadece mainve der malloc. Daha büyük bir programda, bu avlanmak için ciddi bir sıkıntı olacaktır. Bunun nedeni, yürütülebilir dosyanın nasıl derlendiğidir . Neyin yanlış gittiğine dair satır satır detaylar alabiliriz. Programınızı bir hata ayıklama bayrağıyla yeniden derleyin ( gccburada kullanıyorum ):

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

Şimdi bu hata ayıklama derlemesi ile Valgrind , sızan hafızayı tahsis eden tam kod satırını gösteriyor ! (Üslup önemlidir: sizin kaçak olduğu tam olarak olmayabilir, ama neyi sızdırılmış iz bulmanıza yardım eder. Nerede .)

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)

Bellek Sızıntıları ve Hatalarında Hata Ayıklama Teknikleri

  • Ait yararlanın www.cplusplus.com ! C / C ++ işlevleri hakkında harika belgelere sahiptir.
  • Bellek sızıntıları için genel tavsiye:
    • Dinamik olarak ayrılan belleğinizin gerçekten boşaldığından emin olun.
    • Bellek ayırmayın ve işaretçiyi atamayı unutmayın.
    • Eski bellek serbest bırakılmadığı sürece bir işaretçiyi üzerine yenisiyle yazmayın.
  • Bellek hataları için genel tavsiye:
    • Size ait olduğundan emin olduğunuz adreslere ve endekslere erişin ve yazın. Bellek hataları sızıntılardan farklıdır; bunlar genellikle sadece IndexOutOfBoundsException tip problemlerdir.
    • Boşalttıktan sonra belleğe erişmeyin veya belleğe yazmayın.
  • Bazen sızıntılarınız / hatalarınız, tıpkı henüz bir kapanış parantezini yazmadığınızı fark eden bir IDE gibi birbirine bağlanabilir. Bir sorunu çözmek başkalarını çözebilir, bu nedenle iyi bir suçlu gibi görünen bir konu arayın ve bu fikirlerden bazılarını uygulayın:

    • Kodunuzda, bellek hatası olan "rahatsız edici" koda bağımlı olan / bağımlı olan işlevleri listeleyin. Programın yürütülmesini izleyin (belki gdbde belki de) ve ön koşul / son koşul hatalarını arayın. Fikir, ayrılan belleğin ömrüne odaklanırken programınızın yürütülmesini izlemek.
    • "Rahatsız edici" kod bloğunu yorumlamayı deneyin (bu nedenle kodunuz hala derlenir). Valgrind hatası kaybolursa, nerede olduğunu buldunuz.
  • Her şey başarısız olursa, aramayı deneyin. Valgrind'in de belgeleri var !

Sık Kaçak ve Hatalara Bir Bakış

İşaretçilerinize dikkat edin

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)

Ve kod:

#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;
}

Bir öğretim yardımcısı olarak, bu hatayı sık sık gördüm. Öğrenci yerel bir değişkeni kullanır ve orijinal işaretçiyi güncellemeyi unutur. Buradaki hata realloc, tahsis edilen belleği başka bir yere taşıyabildiğini ve işaretçinin konumunu değiştirebileceğini fark ediyor . Daha sonra dizinin nereye taşındığını resizeArraysöylemeden ayrılırız array->data.

Geçersiz yazma

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)

Ve kod:

#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;
}

Valgrind'in yukarıdaki yorumlu kod satırını gösterdiğine dikkat edin. 26 beden dizisi [0,25] dizine eklenir, bu yüzden *(alphabet + 26)geçersiz bir yazmadır - sınırların dışındadır. Geçersiz yazma, tek tek hataların yaygın bir sonucudur. Atama işleminizin sol tarafına bakın.

Geçersiz okuma

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)

Ve kod:

#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 bizi yukarıdaki yorum satırına yönlendiriyor. Buradaki son yinelemeye bakın, yani
*(destination + 26) = *(source + 26);. Ancak, *(source + 26)geçersiz yazma işlemine benzer şekilde yine sınırların dışında. Geçersiz okumalar, tek tek hataların yaygın bir sonucudur. Atama işleminizin sağ tarafına bakın.


Açık Kaynak (U / Dys) topia

Kaçağın benim olduğunu nasıl bilebilirim? Başkasının kodunu kullanırken sızıntımı nasıl bulabilirim? Benim olmayan bir sızıntı buldum; bir şey yapmalı mıyım? Hepsi meşru sorular. İlk olarak, 2 ortak karşılaşma sınıfı gösteren 2 gerçek dünya örneği.

Jansson : JSON kütüphanesi

#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;
}

Bu basit bir programdır: JSON dizesini okur ve ayrıştırır. Yapım aşamasında, bizim için ayrıştırma yapmak için kütüphane çağrılarını kullanıyoruz. Jansson, JSON kendi iç içe yapılarını içerebileceğinden gerekli ayırmaları dinamik olarak yapar. Ancak bu, decrefbize verilen ya da her fonksiyondan hafızayı “serbest bıraktığımız” anlamına gelmez . Aslında, yukarıda yazdığım bu kod hem "Geçersiz okuma" hem de "Geçersiz yazma" atar. decrefHattın çizgisini çıkardığınızda bu hatalar ortadan kalkar value.

Neden? Değişken value, Jansson API'sında "ödünç alınmış bir referans" olarak kabul edilir. Jansson hafızasını sizin için takip eder ve sadece decref JSON yapılarını birbirinden bağımsız olarak yapmanız gerekir . Buradaki ders: belgeleri okuyun . Gerçekten mi. Anlamak bazen zor olabilir, ancak size bunların neden gerçekleştiğini anlatıyorlar. Bunun yerine, bu bellek hatasıyla ilgili mevcut sorularımız var.

SDL : grafik ve oyun kütüphanesi

#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;
}

Bu kodda yanlış olan ne ? Benim için sürekli ~ 212 KiB bellek sızdırıyor. Düşünmek için bir dakikanızı ayırın. SDL'yi açıp kapatıyoruz. Cevap? Yanlış bir şey yok.

İlk başta tuhaf gelebilir . Gerçek şu ki, grafikler dağınık ve bazen bazı sızıntıları standart kütüphanenin parçası olarak kabul etmek zorundasınız. Buradaki ders: her bellek sızıntısını bastırmanıza gerek yok . Bazen sızıntıları bastırmanız gerekir, çünkü onlar hakkında hiçbir şey yapamayacağınız bilinen konulardır . (Bu benim kendi sızıntılarınızı görmezden gelme iznim değil!)

Boşluğa cevaplar

Kaçağın benim olduğunu nasıl bilebilirim?
Bu. (% 99 emin, yine de)

Başkasının kodunu kullanırken sızıntımı nasıl bulabilirim?
Şansı zaten başka biri buldu. Google'ı deneyin! Bu başarısız olursa, size yukarıda verdiğim becerileri kullanın. Bu başarısız olursa ve çoğunlukla API çağrılarını ve kendi yığın izlemenizi çok az görürseniz, bir sonraki soruya bakın.

Benim olmayan bir sızıntı buldum; bir şey yapmalı mıyım?
Evet! Çoğu API'nin hataları ve sorunları bildirmenin yolları vardır. Onları kullan! Projenizde kullandığınız araçlara geri dönmenize yardımcı olun!


Daha fazla okuma

Bu kadar uzun süre benimle kaldığınız için teşekkürler. Umarım bu cevaba gelen geniş yelpazedeki insanlara yönelmeye çalıştığım için bir şeyler öğrendiniz. Umarım yol boyunca sordunuz: C'nin bellek ayırıcısı nasıl çalışır? Bellek sızıntısı ve bellek hatası nedir? Segfaultlardan farkı nedir? Valgrind nasıl çalışır? Bunlardan herhangi birine sahipseniz, lütfen merakınızı besleyin:


4
Çok daha iyi cevap, utanç verici bir cevap değil bu.
A. Smoliak

Böyle bir şey yapmak için iyi bir uygulama olduğuna inanıyorum, birkaç tane kendim yaptım
A. Smoliak

1
Bu cevaba yıldız ekleyebilir ve kendim için gelecekte referans olarak kullanabilir miyim? İyi iş!
Zap

yok memcheckaracı varsayılan olarak etkindir?
abhiarora

@abhiarora Evet. Man sayfası bize memcheckvarsayılan araç olduğunu söyler :--tool=<toolname> [default: memcheck]
Joshua Detwiler

146

Bunu dene:

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

Valgrind kurulduğu sürece programınızdan geçer ve neyin yanlış olduğunu söyler. Size işaretçiler ve sızıntılarınızın bulunabileceği yaklaşık yerler verebilir. Segfault'ing yapıyorsanız, çalıştırmayı deneyin gdb.


"Programınız" ne anlama geliyor? Bu kaynak kodu konumu veya apk dosyası gibi uygulama adı mı?
HoangVu

7
your_program== yürütülebilir ad veya uygulamanızı çalıştırmak için hangi komutu kullanırsanız kullanın.
RageD

27

Koşabilirsin:

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

1

.Bashrc dosyasında aşağıdaki şekilde bir takma ad oluşturabilirsiniz

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

Bellek sızıntılarını kontrol etmek istediğinizde,

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

Bu, geçerli dizinde bir Valgrind günlük dosyası oluşturur.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.