"free() function" csak egyszer működik.

 ( J0se | 2015. június 11., csütörtök - 22:42 )

Hello!

Akadt egy kis problémám a következő nagyon egyszerű kódrészlettel kapcsolatban:

#include stdio.h
#include stdlib.h
#include string.h

void main() {
char *String;
char *String_2;

String = (char *) malloc(255);
String_2 = (char *) malloc(255);

strcpy(String, "Valami");
printf("String = %s", String);
free(String);

strcpy(String_2, "Valami");
printf("String_2 = %s", String_2);
free(String_2);

/*Probléma*/
printf("String = %s\n", String);
printf("String_2 = %s\n", String_2);
}

A probléma a /*Problémánál*/ lép fel, a kimenet pontosan ez:

String = p?v?p?v?
String_2 = Valami

Ebből én arra következtetek hogy a String-nek lefoglalt memóriaterület felszabadult, de a String_2 nem? Rossz a következtetés? Ha nem, akkor mi lehet a baj?

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Az, hogy a lefoglalt memóriaterületet felszabadítod, nem jelenti azt, hogy felülíródik ami a memóriaterületen volt. :)

mindket foglalas felszabadult.

csak veletlen, hogy a String_2 mukodni latszik.

miutan felszabaditottad free()-vel nem szabad hasznalnod a memoriateruletet.

A lényeg, hogy a pointereid értéke változatlan - mintha értékes dolgot mutatnának, pedig "véletlenszerű" valamire mutatnak. Abból ne vonj le semmiféle következtetést, hogy olykor (akár rendszeresen) a valami éppen az, amit a free() előtt odaírtál.

Én valami egészen mást nem értek. Ha felszabadította a memóriát és címez rá, akkor nem kellene elhasalnia segfault-tal a programnak?


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Csak akkor, ha ott butasag van. Attol, hogy felszabaditott, meg nem feltetlenul fog elhasalni.

--
|8]

Nem vagyok programozó, de ez érdekel, mert ezek szerint butaság a fejemben van. Elmondom, mit gondoltam eddig erről, ezek szerint rosszul.

Az alkalmazás foglal memóriát, a malloc() visszaadja a pointert, ahonnan használható. Ha a kért területen belülre címez az alkalmazás, akkor a kernel fizikai memóriát lapoz alá. Ha rossz helyre címez, kivétel keletkezik, a kernel elveszi tőle a vezérlést, felszabadítja az alkalmazás helyét, a foglalt memóriáinak helyét, kinyírja tehát a hibás process-t. Úgy gondolom, free() után már nem az övé a memória, tehát ugyanennek kellene történnie.

Vagy esetleg ez úgy van, hogy ha más által foglalt területre címzünk, akkor van a fent leírt történés, ha nem lefoglalt területre, akkor legfeljebb memória szemetet olvasunk?

Vagy egészen máshogy néz ez ki? Ne hagyjatok tudatlanságban! :)


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Máshogy néz ki. A standard C világkép úgy néz ki, hogy a program adatterületének az egyik végétől nő a malloc() által visszadott "új" világ. Először kb. nulla a mérete, de ahogy allokálgatsz, úgy növeszti a program az operációs rendszertől elkért (belapozott) terület méretét. A malloc() ebből egy-egy darabra ad vissza mutatókat. A felszabadítás viszont csak a program saját hatáskörében történik, azaz a felszabadított területet soha nem adja vissza az operációs rendszernek a program, a már belapozott terület sosem lapozódik ki. Azaz a free() után továbbra is ottmarad a memória, és amíg másra nem allokálod, és felül nem írod, addig még a tartalma is változatlan marad.

Természetesen a malloc/free() implementáció működése ebből a szempontból a környezet belügye, _akár_ meg is írhatják olyanra, hogy adjon vissza memóriaterületeket az operációs rendszernek, de szerintem ez nem standard működés, mivel nyilvánvalóan lassítja a programot már pusztán csak annak a felismerése, hogy lehet-e bármit visszaadni, lévén a lapozás legalább page méretekben történhet, a malloc() pedig ennél kisebb darabokat is kezel.

Ezek szerint a kernel csak akkor kapja vissza a RAM-ot, ha a process kilép, megszűnik?


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

az osszeset igen.

nagy blokkokat hamarabb is visszadhat, pl:

http://www.gnu.org/software/libc/manual/html_node/Efficiency-and-Malloc.html#Efficiency-and-Malloc

de ez malloc()/free() implementaciofuggo, ugye :).

Csak a virtual memory-t nem kapja vissza, amit allokált, a fizikai memoria kilapozható ilyenkor míg újra befoglalásra nem kerül (de lehet hogy csak mmap/munmap esetén, de ez megint allokátor függő így)


// Happy debugging, suckers
#define true (rand() > 10)

A kernel ahogy írtad is, a virtual memory-n keresztűl fizikai memóriát fog a progi alá adni. Viszont ha te egyszer 255 byte -ot foglalsz, akkor is egy teljes page kerül alád (a következő 255 byte malloc pedig majd ugyan ebbe a page-be kerül míg van benne hely, aztán kapsz másikat ha elfogyott). Ráadásként a free -vel valóban felszabadítod a memóriát, amit később újrafoglalhatsz, viszont a free nem jelenti azt hogy azonnal visszakerül a kernel kezébe a page, így ha később véletlen bele is írsz, nem biztos hogy azonnal segmentation fault az eredménye. Ráadásként ezek erősen allocator függő dolgok, a tcmalloc -nak pl rá kell csapni a kezére időnként hogy legalább a kernelig visszapasszolja az általa lefoglalt területeket


// Happy debugging, suckers
#define true (rand() > 10)

Elhasal, ha talál egy olyan hosszú "sztringet" a kiíráskor, ami valahol idegen területre lóg - addig védenek (és tévhitben tartanak) a kóbor 0-ák.

Aha. Tehát, ha jól értelek, nem lefoglalt területre szabad címezni, azt hagyja a kernel, az alkalmazás vessen magára. Akár írható is, gondolom, de lehet, el fogja lapozni a kernel, szóval ilyen területre nyilván nem lehet számítani. Ha viszont más által lefoglalt területre írna, vagy onnan olvasna adatot az alkalmazás, akkor a kernel SIGSEGV-vel beszántja.

Jól gondolom? Vagy legalább is ez már közelebb van az igazsághoz?


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

A malloc(3)-ból nem feltétlenül lesz rendszerhívás (sbrk(2) vagy mmap(2)), a free-ből pedig még ritkábban... tipikusan a libc illetékes része a free(3) után is megtartja a memóriát későbbi használatra.

Tehát, ha jól értelek, a kernelnek nem is adja vissza a memóriát minden esetben a free(). Tehát legfeljebb RAM-szemét lesz, amit olvas az ember, de a kernel úgy tudja, azt a területet ez a process kérte el, tehát nem cirkuszol, csak akkor, ha másik process területébe próbálnánk belepiszkálni.


tr '[:lower:]' '[:upper:]' <<<locsemege
LOCSEMEGE

Icipicit módosítottam a kódon:

void main( int argc, char* argv[]) {
   char *String;

   String = (char *) malloc(255);

   strcpy(String, "Valami1");
   printf("String = %s\n", String);
   printf("StringP = %x\n", String);
   free(String);

   printf("- - - - - - - - - -\n");

   printf("StringP = %x\n", String);
   for ( int i=0; i<atoi(argv[1]); ++i) {
      printf("%d\n", i);
      strcpy( String + i*10, "0123456789");
   }
   printf("String: %s\n", String);
}

Aztán elkezdtem hívogatni 10 hatványaival.
10000-ezerig simán elment (noha 255 bájtos volt a foglalás), de végül beteljesedett a sorsa 100000-rel:

13514
13515
Segmentation fault (core dumped)

Vagyis vagy tényleg szabad hülyének lenni a heapen, vagy (ezt sem merem kizárni) mostanában, amikor ANNYI a RAM, a kernel alattomosan provízionál akkor is, ha senki sem kéri.

mire segfaulthoz ert nekem pont akkora lett a heap:

cat /proc/16716/maps

00400000-00401000 r-xp 00000000 00:11 256836 /tmp/test
00600000-00601000 rw-p 00000000 00:11 256836 /tmp/test
00601000-00622000 rw-p 00000000 00:00 0 [heap]
7ffff7a33000-7ffff7bd2000 r-xp 00000000 08:22 5183 /lib/x86_64-linux-gnu/libc-2.19.so
7ffff7bd2000-7ffff7dd2000 ---p 0019f000 08:22 5183 /lib/x86_64-linux-gnu/libc-2.19.so
...

0x622000 - 0x601000 = 0x21000 = 135168

szoval minden kerek.

Stimm.
Örülök, hogy a témába botlottam.

ja, hat ezt en sem tudtam, szoval utananeztem: alapbol heap nelkul indul, aztan M_TOP_PAD kvantumokkal noveli:

http://www.gnu.org/software/libc/manual/html_node/Malloc-Tunable-Parameters.html#Malloc-Tunable-Parameters

es man mallopt-bol kiderul, hogy

M_TOP_PAD
...
The default value for this parameter is 128*1024.

(szoval a glibc provizional, es ha fontos meg tudod szabni, hogy mennyit.)

A lenti, avatottabb kollégák kommentjei alapján a "provisioning" verzió áll (a processz kap egy 132k heapet (szépen ki is jön, hogy a 13514. tízbájtos szelet után bukik ki), és csak azon belül lehet heveskedni.

Vagyis én éltem eddig téves feltételezéssel: nem kell más processz útját keresztezni a segfaulthoz.

Aha, így már értem!
Köszönöm a segítséget! :)

Ahogy már többen is leírták, a programod működhet így, mivel felszabadított memóriaterületen nem garantált, hogy mi van éppen.
Én annyit jegyeznék meg, hogy:
- a felszabadított pointereket érdemes azonnal NULL-ra állítani. Akkor biztosan kiderül a hiba dereference-nél. Ha pedig véletlen megpróbálod még egyszer free()-zni, az ártalmatlan! (de nem így egy nem NULL, már felszabadított pointer esetében).
- ha valgrind-al ellenőrzöd a program memóriafelhasználását, akkor ő ki fogja neked dobni hibaként, amikor a felszabadított területekről olvasol.