Finde C/C++ fejl med valgrind -> massif finder fejl i allokering/deallokering

Jeg har haft meget travlt på arbejdet de sidste par uger, især med at få releaset en stor klump kode, hvor mange har deltaget i af kode. Det kommer fra tid til anden fejl ind. Vi diskuterer langt over 100.000 linier kode på en Linux-maskine ![Eksternt billede](http://www.version2.dk/uploads/smil3dbd4d6422f04.gif" alt=")

Anyway - jeg har haft stor glæde af valgrind til at finde array/pointer fejl som jeg tidligere har skrevet om. Valgrind (primært til Linux) kan dog en del andet, og dette er fokus for dette og det næste blog-indlæg, som nok kommer i morgen.
En af de C/C++ fejl, som jeg også jævnligt ser er allokering af hukommelse, som ikke frigives korrekt. Når jeg kører programmet ser jeg at det spiser mere og mere hukommelse.

Lad mig give et eksempel

I C-eksemplet her under har jeg fire underfunktioner func1-func4, som returnerer et heltal - selve de funktioner er her barberet ned til næsten ingenting. I eksemplet er main() functionen uden egentligt indhold - det vigtige er at man kalder hver af de fire func1-func4 funktioner. Når man kører programmet vælger man med første argument hvor mange iterationer man kører (jeg ved godt at programmet seg-faulter hvis man ikke giver et argument).

I min rigtige kode har jeg flere tusinde funktioner, med masser af
niveauer.

Den opgave jeg vil løse er at finde hvilken af de fire funktioner, som glemmer at afgive hukommelsen. Jeg har markeret den slemme funktion med "Nej nej nej" nedenfor (det er funct2 som mangler en free() til sidst.

#include   
#include 

int func1(int i)  
{  
  return i+1;  
}

int func2(int i)  
{  
  int *j;  

  j = (int *)malloc(1000000); /* Nej nej nej */  
  return i+2;  
}

int func3(int i)  
{  
  return i+3;  
}

int func4(int i)  
{  
  return i+4;  
}


int main(int argc, char** argv)  
{  
  int ii,sum;  
  int iters = atoi(argv[1]);  
  printf("Running %i iterations : %s\n",iters,argv[1]);  
  for (ii=0;ii

Lad os oversætte koden (navngivet "v2.c") og se problemet

$ gcc -o v2 v2.c
$ ./version2 10000

Efter få sekunder er der spist et par GB hukommelse - ikke godt.

Naturligvis er det nemt i dette tilfælde at pege på func2() som den skyldige, men lad mig prøve med valgrind. Valgrind er ikke bare et program, men en familie af "tools". I dette tilfælde anvender jeg "massif" og jeg kører kun 1000 iterationer.

$ gcc -o v2 v2.c
$ valgrind --tool=massif ./version2 1000
....
Iteration 994 : sum=3996
Iteration 995 : sum=4000
Iteration 996 : sum=4004
Iteration 997 : sum=4008
Iteration 998 : sum=4012
Iteration 999 : sum=4016
==5448==

Til slut står der et ID-nummer fra valgrind. Der er nu en tilsvarende fil i samme katalog "massif.out.5448" som kan fødes til "ms_print"

$ms_print massif.out.5448

![Eksternt billede](http://www.version2.dk/modules/xphoto/cache/56/7656_450_600_0_0_0_0.png" alt="massif)

Der er et lineært stigende hukommelsesforbrug, som vidner om en malloc() som ikke har en tilsvarende free(). Hver gang der er et "@" følger en detaljeret analyse nedenfor, såsom denne:

![Eksternt billede](http://www.version2.dk/modules/xphoto/cache/56/7658_450_700_0_0_0_0.png" alt="massif)

Som det ses i næst-sidste linie peges direkte på func2() linie 14 som den skyldige. I et større kode-kompleks får man naturligvis masser af allokeringer, så det bliver lidt mere "grisset" at kigge igennem, men min klare erfaring er at massif er et glimrende værktøj til denne typer af problemer.

I mit næste blog-indlæg ser jeg nærmere på tidsforbrug splittet op på funktionerne. Dette er også rigtig rart at kunne analysere systematisk.

/pto

Kommentarer (18)
sortSortér kommentarer
  • Ældste først
  • Nyeste først
  • Bedste først
Lars Lundin

Valgrind kører på Mac siden v. 3.5, siden v. 3.6 kører valgrind også på ARM(v7) så nu kan du også efterspørge valgrind erfaring på Nokia N900. :-)

Jeg forstod ikke så godt eksemplet med Massif - kunne man ikke ligeså godt finde den manglende free() med Memcheck ?

Jeg forestiller mig at Massif i forhold til Memcheck er mere fordelagtig til optimering af allokeringerne.

Hvem har forresten haft fordel af exp-ptrcheck ?

  • 0
  • 0
Peter Toft

Du har ret i at "valgrind --tool=memcheck --leak-check=full ./v2" også finder
==11153== 6,000,000 bytes in 6 blocks are definitely lost in loss record 2 of 2
==11153== at 0x4025BD3: malloc (vg_replace_malloc.c:236)
==11153== by 0x8048440: func2 (v1.c:13)
==11153== by 0x80484D3: main (v1.c:36)

Det du ikke kan med memcheck er at vurdere størrelsen af problemet eller hvornår det kommer.
Prøv f.eks. at indsætte "if (i>50)" foran malloc-sætningen - og gentag hvad jeg viste. Med massif ser man tydeligt at det går galt efter lidt tid.

Især den måde man kan få en dynamisk hukommelses-analyse tiltaler mig.

  • 0
  • 0
Lasse Reinholt

Valgrind kører på Mac siden v. 3.5, siden v. 3.6 kører valgrind også på ARM(v7) så nu kan du også efterspørge valgrind erfaring på Nokia N900. :-)

I øvrigt er det ikke sikkert, at Maemo free'er alt når din process terminerer, som man er vant til fra Windows, Mac, Solaris, osv. Så det er vigtigt at få muget ud i dette også.

Nåja, og jeg kunne også benytte lejligheden til at reklamere lidt for en lille trådtester, jeg skrev: http://pastebin.com/wDDWPpd9

Copy/paste det ind efter include <pthread.h> og kør programmet. Det vil fremprovikere visse typer af trådfejl, som Valgrind (med --tool=helgrind eller --tool=drd) ikke fanger.

Det skal lige cleanes lidt. Lige nu er det fx kun trylock, som kan returnere et argument.

  • 0
  • 0
Lasse Reinholt

Kan du uddybe det?

Det var bare, at hukommelse, som du malloc'er men ikke free'er, automatisk bliver free'd når processen terminerer på de fleste server/desktop OS'er.

Hvis dit leak har en statisk størrelse (i modsætning til Peter Tofts eksempel), så er der mange, der derfor bare ignorerer dem.

Og det skal man så passe med på embeddede og mere specielle OS'er.

  • 0
  • 0
Lars Lundin

Det var bare, at hukommelse, som du malloc'er men ikke free'er, automatisk bliver free'd når processen terminerer på de fleste server/desktop OS'er.

Også under Linux, som er det OS Meego bygger på.

Kan du komme med et eksempel på et OS, hvor dette ikke er opfyldt?

Hvis dit leak har en statisk størrelse (i modsætning til Peter Tofts eksempel), så er der mange, der derfor bare ignorerer dem.

Og det skal man så passe med på embeddede og mere specielle OS'er.

Undskyld, men det lyder som en omgang vrøvl.

  • 0
  • 0
Anonym

Undskyld, men det lyder som en omgang vrøvl.

Hvorfor dog det? Det er da helt normalt, at hvis man ikke deallokerer dynamisk allokeret lager, så vil dette ikke nødvendigvis blive deallokeret, når en proces terminerer.

Man kan så argumentere for, at det ikke betyder så meget i indlejrede systemer, da disse normalt ikke indeholder processer der terminerer og ofte er dynamisk allokering slet ikke tilladt.

  • 0
  • 0
Lasse Reinholt

Kan du komme med et eksempel på et OS, hvor dette ikke er opfyldt?

Dem, som ikke er processorienterede, VxWorks f.eks.

Undskyld, men det lyder som en omgang vrøvl

Æh, Ok. Det er void main(void){malloc(1000);} som jeg taler om. Ved hvilket trin er det, din kæde hopper af?

  • 0
  • 0
Lars Lundin

Ved hvilket trin er det, din kæde hopper af?

Ja, diskussionen tog udgangspunkt i en proces, som kaldte malloc() og derefter terminerede uden at kalde free().

Det var mit indtryk at styresystemet havde sin egen information om processens allokering (og at denne information kom f.eks. når processen kaldte brk()), og at styresystemet derfor kan være ligeglad om processen kalder free() eller ej inden den terminerer.

Men der var åbenbart noget jeg har misforstået - det må I undskylde.

  • 0
  • 0
Lars Lundin

Er pointen ikke med C/C++ at nogle gange vil man netop kunne returnere malloc() hukommelsen og i andre tilfælde ikke - derfor er der ikke en automatisk free().

Jo, helt sikkert.

Kan du måske forklare mig under hvilke omstændigheder en proces kan allokere hukommelse og så terminere uden at denne hukommelse bliver frigivet ?

  • 0
  • 0
Michael Balle-Pedersen

At "normale" operativsystemer automatisk rydder op efter en proces' hukommelsesforbrug, betyder jo ikke at der ikke kan findes operativsystemer der ikke har den egenskab. Tror bare det var det Lasse refererede til.

Det er jo ikke unormalt at programmer er afhængige af denne oprydning. Programmet kan afslutte hurtigere hvis det ikke explicit skal rende rundt og frigive al anvendt hukommelse, men bare kan få systemet til at klare oprydningen.

Husker bl.a. at det engang voldte problemer for et firma der udviklede IDE's, da de ville bruge en compiler som var vantil at systemet ryddede op efter den. Det gav anledning til en del leaks da koden til den kompiler blev indsat i et IDE der ikke tilbød den opførsel.

  • 0
  • 0
Jens Dalsgaard Nielsen

Den finder den på 1 u sec :-)

Jens

~/shit$ splint tt.c
Splint 3.1.2 --- 03 May 2009

tt.c: (in function func2)
tt.c:14:18: Fresh storage j not released before return
A memory leak has been detected. Storage allocated locally is not released
before the last reference to it is lost. (Use -mustfreefresh to inhibit
warning)
tt.c:13:7: Fresh storage j created
tt.c: (in function main)
tt.c:28:18: Parameter argc not used
A function parameter is not used in the body of the function. If the argument
is needed for type compatibility or future plans, use /@unused@/ in the
argument declaration. (Use -paramuse to inhibit warning)
tt.c:4:9: Function exported but not used outside tt: func1
A declaration is exported, but not used outside this module. Declaration can
use static qualifier. (Use -exportlocal to inhibit warning)
tt.c:7:5: Definition of func1
tt.c:9:9: Function exported but not used outside tt: func2
tt.c:15:5: Definition of func2
tt.c:17:9: Function exported but not used outside tt: func3
tt.c:20:5: Definition of func3
tt.c:22:9: Function exported but not used outside tt: func4
tt.c:25:5: Definition of func4

Finished checking --- 6 code warnings

  • 0
  • 0
Lars Lundin

Jeg brugte splint engang, men det kan ikke klare C99 og dets udvikling har stået stille i flere år.

PHK har skrevet positivt om et andet statisk check-værktøj, flexelint - som dog kræver betaling for en licens.

  • 0
  • 0
Log ind eller Opret konto for at kommentere