[MEGOLDVA] Memleak fgets korul

Fórumok

Nekilattam leporolni kicsit az egyre csak avulo C tudasomat, es elkezdtem egy regebbi projektet rendberakni. Semmi extra, egy Linuxos MD5 ellenorzoosszeg ellenorzo OSX -re.

A problemam lenyege abban foghato meg, hogy probalom tuzzel, kereszttel es szenteltvizzel irtani a memleakot a progibol. Ehhez a Valgrind nevu utility-t hasznalom, mellyel korabban szoros baratsagot kotottem.

Az inkriminalt kod emigyen nez ki: http://sprunge.us/XKFY

Ami a problema, hogy az fgets() hivas kornyeken a derek valgrind memleakot jelez. Viszont gozom nincs, honnet veszi. Utolso emlekeim szerint a fgets-nek elore inicializalt karaktertombot kell adni, mert kulonben nem mukodik az egesz A karaktertombot - mivel relative pici - a stackrol veszem, de malloc-cal is ugyanez a helyzet, az fgets-nel memleakot jelez, hiaba van valahol nagyon lent egy free().

Rosszul emlekszem az fgets-sel kapcsolatban? Vagy itt mas turpissag van?

Log:


==50098== 4,096 bytes in 1 blocks are definitely lost in loss record 83 of 85
==50098==    at 0x5237: malloc (in /usr/local/Cellar/valgrind/3.8.1/lib/valgrind/vgpreload_memcheck-amd64-darwin.so)
==50098==    by 0x2C80A4: __smakebuf (in /usr/lib/system/libsystem_c.dylib)
==50098==    by 0x2C8C2D: __srefill0 (in /usr/lib/system/libsystem_c.dylib)
==50098==    by 0x2C8D7B: __srefill (in /usr/lib/system/libsystem_c.dylib)
==50098==    by 0x2C4EBC: fgets (in /usr/lib/system/libsystem_c.dylib)
==50098==    by 0x100000A81: checkMD5 (md5sum.c:39)
==50098==    by 0x1000010BD: main (md5sum.c:113)

Hozzászólások

lehet OS bug, de szvsz ami system-ban jelez errort, az figyelmen kivul hagyhatod ugyse tudsz vele mit tenni...

--
A vegtelen ciklus is vegeter egyszer, csak kelloen eros hardver kell hozza!

fclose() csodakra kepes ;) A Standard I/O library a __smakebuf() hivasaval egy buffert allokal az altalad megnyitott stream-nek, ha a stream-et nem zarod le, akkor ez a buffer nem szabadul fel es ezt jelzi a Valgrind.

Lehet, hogy maradi vagyok, de én az fopen fflush+fclose fv-eket
úgy használom, mint a zárójelpárokat: ha leírom, hogy fopen,
akkor a következő sorba az jön, hogy fflush, fclose. A két sor
közé pedig beírom amit a fájl-lal csinálni akarok.

Ha más eljárásban nyitok, mint ahol zárok, akkor is nyitás után
először a záró eljárást írom meg, utána foglalkozom a
fájlfeldolgozással.

Ugyanígy használom a malloc-free, create-destroy stb párokat.

> Sol omnibus lucet.

Ruby eseteben en megszoktam, hogy van olyan, hogy open blokk, vagyis valami ilyesmi:


File.open("enfajlom") do |fp|
end

es a rendszer maga zarja a fajllt a blokk vegen.

A malloc-cal valo viszonyom kulonosen erdekes. az a baj, hogy a legtobbszor nem tudom, hogy hol fogok kijonni a fuggvenybol, sokszor 3-4 helyen is ki tudok jonni belole, es nem mindig jut eszembe, hogy na, oda kell egy free is. Meg, aki folyamatosan GC-s (vagy olyan szeru) nyelvekkel dolgozik, annak ezek nincsenek annyira a vereben, hiszen ugyis jon majd a kukasauto, es elviszi a lomot. Pontosabban, annak ezek inkabb teljesitmenytuningolasi pontok semmint kotelezo elemek.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

(shake) nem feltetlen. Peldaul ha van egy feldolgozando fajlod, ami nem letezik, akkor egyszerubb meg az elejen kiugrani az egesz metodusbol.

Es ha 1000 soros metodusod van, akkor valamit rosszul csinalsz.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Nezd, en probalkoztam mar vele, en is igyekszem arra torekedni, hogy lehetoleg egy be- es egy kijarat legyen, de sokszor csak feleslegesen bonyolitja a dolgokat, es olvashatatlanna teszi a kodot, ha 3-4 if blokkon belul helyezkedik el a tenylegesen feldolgozo kod. Akkor inkabb a korai kilepes.

Es, mint mondtam, olyan nyelveknel, ahol van GC (pontosabban ahol az objektumok maguktol megsemmisulnek, akar GC akar referencia szamolas miatt), ott nincs is szukseg ilyen megkotesekre.

Ja, es just FYI: attekinthetoseg != kodminoseg. Lehet egy atlathatatlan kod is jo minosegu, es lehet egy attekintheto kod is pocsek.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

A programozói stílusok különböznek, nincs ezzel gond. Ráadásul a
stílust és a felhasználta eszközöket a feladat is befolyásolja.
Nagyon fontos, hogyha csapatban dolgozol, hogy alkalmazkodnod kell,
ez is módosítja a kódot.

Nekem szerencsém van, alkalmazkodnom nem kell.

> Sol omnibus lucet.

A formazas olyan dolog, amit az IDE-re bizok. Majd szepen beformazza a kodot az eloirasok alapjan, amit megbeszeltunk a projekten. En meg olyan modon irom, ahogy nekem jo, a verziokezelobe mar jo formazassal kerul be.
Amugy ezt forditva is jo lenne tudnia az IDE-knek: a verziokezelobol az en formazasom szerinti kodot nyitja meg, de elmenteni mar jot ment el. Igy mindenki ugy latja a kodot, ahogy neki jo, de az egesz projekt formazasa attol meg egyseges.

Nem, en forditott workflow-ra gondolok. Megnyitod IDE-ben a fajlt, beformazod magadnak, szerkeszted, mented. Amikor pedig felcommitolnad, akkor ujraformaztatik a fajl a repo coding standardjanak megfeleloen.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Nem, en forditott workflow-ra gondolok. Megnyitod IDE-ben a fajlt, beformazod magadnak, szerkeszted, mented. Amikor pedig felcommitolnad, akkor ujraformaztatik a fajl a repo coding standardjanak megfeleloen.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Ha megertesrol van szo, akkor nyugodtan atformazhatod. Ha mar erted, akkor undo minden, es megirod a project Coding Standardja szerint amit meg akarsz irni.

Lent javasolta valaki checkout/checkin-kori automatikus konverziot, de szerintem az sem jarhato ut, mert ha egy csapaton belul mindenki ugy formazza ahogy kedve tartja, akkor soha sem fognak egy kod reszletrol igazan elbeszelgetni, mert mindenki mast lat a sajat kepernyojen, es senki se erti mi van mas kepernyojen...

Ujraformazgatsz amig hozzaszoksz, egy ido utan barmilyent meg lehet szokni, gondolom nem minden heten mas projekten dolgozol kulombozo kod formatalassal, nem?

En nem dolgozok a projekteken, hanem hibat keresek bennuk. A legtobbszor az a problemam, hogy azzal keresnek fel, hogy ez es ez a webapp nem megy, ugyan nezzem mar meg, mi baja, mert a fejleszto az nincs/nem er ra/draga lenne. Es sokszor meg a db konfigra is vadaszni kell.

Es nem naponta, akar orankent mas projekten dolgozok kulonbozo formazassal.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Hat nem, sajnos ezen a teren egyaltalan nem fejlodtem. Mak, hogy a legtobb programozopalanta legalabb addig eljut, hogy IDE-t hasznaljon, azok meg altalaban nyurrognek, ha nem megfelelo formazassal dolgozol, illetve az auto-indentation legalabb neminemu stilust kikovetel.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

"Nezd, en probalkoztam mar vele, en is igyekszem arra torekedni, hogy lehetoleg egy be- es egy kijarat legyen, de sokszor csak feleslegesen bonyolitja a dolgokat, es olvashatatlanna teszi a kodot, ha 3-4 if blokkon belul helyezkedik el a tenylegesen feldolgozo kod. Akkor inkabb a korai kilepes.
"

nezd meg a fenti irast, ilyenkor pont hogy jol hasznalhato a goto.

"Es, mint mondtam, olyan nyelveknel, ahol van GC (pontosabban ahol az objektumok maguktol megsemmisulnek, akar GC akar referencia szamolas miatt), ott nincs is szukseg ilyen megkotesekre."

ja, a trabant 60-nal megy, a concorde meg 1200-zal.
most c-rol van szo, abban nincs gc.
ha nem tetszik, hasznalj mast.
vagy minek irod mar tobedszerre a gc-t???
nincs gc, nem is lesz.

erre valók az exceptionok.

c esetén goto-val emulálhatod és faszányos hibakezeléseket írhatsz.
hűű, de rég is volt már, hogy c-ztem... :)

ha a goto-t ordogtol valonak tartod, akkor hasznalhatsz if szerkezeteket is, de akkor sokkal atlalthatatlanabb lesz a kod.

nezd:

http://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-i…

" Hidd el, azert nem ma kezdtem el programozni."
pedig ugy tunik...

> erre valók az exceptionok. c esetén goto-val emulálhatod és

http://code.google.com/p/exceptions4c/


#include "e4c.h"

int foobar(){

    int foo;
    void * buffer = malloc(1024);

    if(buffer == NULL){
        throw(NotEnoughMemoryException, "Could not allocate buffer.");
    }

    try{
        foo = get_user_input(buffer, 1024);
    }catch(BadUserInputException){
        foo = 123;
    }finally{
        free(buffer);
    }

    return(foo);
}

Nem olyan nehéz sajátot írni, annak olyan licence lesz, amilyet csak szeretnél :-)
Ebből elleshető az alapötlet:


#include <stdio.h>
#include <setjmp.h>
 
static jmp_buf exception_buf;
#define TRY if(setjmp(exception_buf) == 0)
#define CATCH else
#define THROW() longjmp(exception_buf, 1)
 
int main(void)
{
  TRY
  {
    puts("I am in a try block.");
    THROW();
    puts("I will not be seen.");
  }
  CATCH
  {
    puts("Exception caught.");
  }
 
  return 0;
}

Hibakezeles hagyomanyosan C-ben:

int fv() {
unsigned char* a = malloc(128);
int error;

error = foo(a);
if (error) {
fprintf(stderr, "foo() failed: %s\n", strerror(error));
goto out;
}

error = bar(a);
if (error) {
fprintf(stderr, "bar() failed: %s\n", strerror(error));
goto out;
}

(void )baz();

out:
free(a);

return error;
}

--
NetBSD - Simplicity is prerequisite for reliability

int fv() {
int error;

unsigned char* a = malloc(128);
if (a == NULL) {
error = ENOMEM;
goto out;
}

error = foo(a);
if (error) {
fprintf(stderr, "foo() failed: %s\n", strerror(error));
goto out;
}

error = bar(a);
if (error) {
fprintf(stderr, "bar() failed: %s\n", strerror(error));
goto out;
}

(void )baz();

out:
if (a != NULL) {
free(a);
}

return error;
}

:p

--
NetBSD - Simplicity is prerequisite for reliability

Egy kerdes ami felmerult bennem: a free() implementacioja nem lehetne olyan, ami a double free-hibakat megszunteti?
Ugyanugy, ahogy a malloc() sem ad ki foglalt teruletet meg egyszer. Hanem jelzi, hogy elfogyott a memoria.
Ha egy nem lefoglalt memoriateruletet akarnal felszabaditani (azaz amire mar a free() meg lett hivva), akkor nem csinal semmit.

Nem erre gondolok, ez nem megoldas teljesen.
Azt akarom elkerulni, hogy ha atadok egy teljesen ismeretlen, 3rd party, rosszul dokumentalt kodnak egy pointert, akkor biztonsaggal tudjam felszabaditani utana a pointeremet, akar felszabaditotta a 3rd party, akar nem. Nem bizhatok abban, hogy a 3rd party felszabaditotta, vagy sem.

Na, tenyleg kerdes: miert nem lehet megirni azt a free()-t ugy, hogy ha azt eszleli, hogy egy mar felszabaditott (azaz nem a program kezelese alatt allo) buffert akarnek felszabaditani, akkor nem csinal semmit?
A legtobb indok arra vonatkozik a double free kapcsan, hogy a malloc altal hasznalt adatstruktura lesz korrupt. Miert nem lehet (van barmifele akadalya) ugy implementalni a free()-t, hogy ha olyan buffert akarnal felszabaditani, ami nincs is foglalva a program altal, akkor nem csinal semmit? Tenyleg erdekel a kerdes.

a free() egy eleg egyszeru fuggveny, egyetlen parameter van: egy pointer. az ezen a cimen talalhato memoriateruletet szabaditja fel. egyeb parameter vagy plusz informacio hianyaban honnan tudna, hogy itt mar nincs szamara hasznos dolog?

sok megoldas lehetne: valahogy jelolje meg a valtozot, hogy itt mar nincs teendo, vagy valahol legyen nyilvantartva a lefoglalt/nem lefoglalt memoriaterulet. ez nem uj talalmany, ugy hivjak hogy garbage collection

amikor a nyelvet megalkottak, valoszinuleg a GC eleg eroforras igenyes volt, igy inkabb a programozora biztak a memoriakezelest

---
Egy jól megállapított probléma félig megoldott probléma.
- Charles Kettering

Nos, hat igen, csak azota valtozott a vilag. Tenyleg neha azt erzem, hogy csak azert, mert szabvany, mindenfele hulyesegbe vagyunk belekenyszeritve. Nem azt mondom, hogy a szabvanyok rosszak, csak azt kelleen eszrevenni, hogy idovel elavulnak. De ez privat velemeny, egy olyan kodertol, aki nem programozo.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

mert a c nem igazan magaszszintu nyelv, azt csinalja amit mondasz neki.
ilyen jelleguen nem sokat gonolkozik helyetted...

ha nagyon ez kell neked, akkor itt van, hogy a glibc-ben hogy tudod megoldani:
http://www.delorie.com/gnu/docs/glibc/libc_34.html

aztan irsz erre egy libet, ami forditaskori ifdef-ekkel eldonti, hogy min fordul, es annak a c libnek megfelelo kodot forditja bele...

es biztos van mar ra kesz megoldas, ugyhogy keress ra.

A malloc(), calloc() es tarsai kelloen okosan, ugymond magasszintuek olyan tekintetben, hogy ket egymas utani hivasuk nem adja vissza ugyanazt a memoriateruletet. Nyilvan ezt valamifele memoriateruleten tarolja is, sok esetben lefoglalt blokkok lancolt listajaval. Akkor a free(), ami ezeknek a parja, az allokator altal hasznalt informaciokat miert nem tudja felhasznalni a helyes mukodeshez? Ha a malloc() tud hibamentesen mukodni, akkor a free() miert nem?

A safe programming alapja (es a keretrendszereknek igy kellene mukodnie), hogy minden bemenetet elfogad, legfeljebb nem csinal semmit, a kimenete pedig mindig helyes.
Pont ez a lenyeg: publikus API-nal nem teszunk semmilyen feltevest az inputra vonatkozoan, a fuggvenyen belul validaljuk az argumentumokat, az invalidokat pedig elutasitjuk. Igy biztosithato, hogy akarmilyen felelotlen volt a programozo, aki dolgozott, nehezebben tudja magat labon loni, legfeljebb a sajat kodjaval.

Attol alacsonyszintu a malloc/free, hogy nem csinal semmi foloslegeset. Es nem, egy alacsonyszintu keretrendszernek nem ugy kellene mukodnie, hogy barmit elfogad. Arra van egy halom magas szintu keretrendszer. Ami a publikus API-kat illeti, annyi felvetest tegyen az input-ra vonatkozoan, amennyi dokumentalva van, semmi tobbet es semmi kevesebbet. A tobb felvetesbol fagyas lehet, a kevesebbbol meg folosleges lassulas.

Ha felelotlen a programozo akkor valasszon mas (magasabb szintu) nyelvet. En azt szeretem, ha minel hamarabb kifagy a programom ha valamit elnezek, ne probalja nekem lekezelni, mert majd ha a holdallas kedvezotlen, akkor jonnek majd a nehezen reprodukalhato memoria korupcios hibak, ami utan lehet majd jo sokat keresgelni....

"Meg, aki folyamatosan GC-s (vagy olyan szeru) nyelvekkel dolgozik, annak ezek nincsenek annyira a vereben, hiszen ugyis jon majd a kukasauto, es elviszi a lomot."

Na azért ez nem ennyire egyszerű, vannak más erőforrások is a memórián kívül, amikről illik gondoskodni. Pl. C#-ban ott a using blokk, kb. ugyanazt tudja mint amit írtál rubys példa.

+1

Tényleg nem értem, hogy emberek miért küzdenek a C-vel olyan területeken ahol simán használható a C++.
Mivel a C++ tartalmazza a C 95%-át a legtöbb C kód automatikusan C++ is.
És rögtön használható pl a RAII.

Nem tetszik az exception? Ne használd. Nem tetszik az operator overloading? Ne használd. Nem tetszik a template, az OOP? Ne használd.
Használd azokat a plusz feature-öket amiket értesz és amik megkönnyítik az életed.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Ami az aláírásból hiányzik az az, hogy C közelébe sem engedném az átlagos programozót.
Sőt néha az az érzésem, hogy az átlag már olyan alacsony, hogy szimplán csak ki kéne rúgni mindenkit egy szint alatt ezzel emelve az átlagot...

De a viccet félretéve, a C++ mint egész egy rohadt nehéz nyelv, főleg ha egyszerre zúdítják rá valakire. Viszont C irányból van egy kényelmes és logikus út az elsajátításához: lassan, feature-ről feature-re haladva.
És ezen az úton bárhol meg lehet állni... (Csak ekkor ne higgyük magunkat C++ expertnek :) )

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee." -- Ted Ts'o

Hat a C++ tenyleg nem egy egyszeru allat - en is inkabb csak azokat a reszeit hasznal(gat)om (jol/rosszul, ezt most ne firtassuk) amik a C hasznalatat konnyitik meg. Kicsi OOP, par osztaly, de semmi bonyolult.

Utolag belegondolva tenyleg egyszerubb lett volna ezt a cuccot C++-ban elkezdeni, azonban most semmire se vagyok kevesbe, mint ezt ujrairni. Az alap koncepcio az volt, hogy eleg sokszor toltok le olyan cuccokat a netrol, aminek Linuxos md5sum/sha256sum -mal generaltak a checksum fajljat, es macre nem igazan talaltam semmit, ami ezt a formatumot enne. Mivel akkor, mikor elkezdtem, kezdtem el komolyabba ismerkedni a C-vel, ugy gondoltam, ez jo gyakorlat lesz. Hat, vegulis az lett, de ma mar eleve nem igy kezdenek hozza a cucchoz. Az pl. a mai napig vicces, hogy a sha256sum kodja nagyreszt egy nem publikalt sed scripttel generalodik az md5sum forrasabol.
--

Ki oda vagyik, hol szall a galamb, elszalasztja a kincset itt alant. | Gentoo Portal 

Toredelmesen bevallom, valoszinuleg en ertettelek felre, mivel a "Ha más eljárásban nyitok, mint ahol zárok" resz folott nemes egyszerusseggel atsiklottam ;) es enelkul a dolog ugy nezett volna ki, hogy az fgets()-nek a visszateres elott egy free()-t kellett volna hivni, ami nyilvan baromsag.