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.
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.
Yanıtlar:
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)
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 main
ve 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 ( gcc
burada 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)
IndexOutOfBoundsException
tip problemlerdir.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:
gdb
de 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.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ı resizeArray
söylemeden
ayrılırız array->data
.
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.
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.
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.
#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, decref
bize 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. decref
Hattı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.
#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!)
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!
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:
memcheck
aracı varsayılan olarak etkindir?
memcheck
varsayılan araç olduğunu söyler :--tool=<toolname> [default: memcheck]
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
.
your_program
== yürütülebilir ad veya uygulamanızı çalıştırmak için hangi komutu kullanırsanız kullanın.
Koşabilirsin:
valgrind --leak-check=full --log-file="logfile.out" -v [your_program(and its arguments)]
.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.