InfoC adventi naptár
Változó hosszúságú tömbök
Tegyük fel, hogy a programunkban változó méretű tömböket használunk. Legyenek
a tárolt adatok double
típusúak. Minden lefoglalt memóriaterülethez
a tömb méretét is természetesen meg szeretnénk jegyezni. Mivel egy lefoglalt
memóriaterület (a tömb maga), és a méret két nagyon erősen összetartozó adat,
az egészet egy struktúrába tesszük:
typedef struct { int meret; double *adat; } DinTomb;
Így a függvényeinknek is kényelmesen át tudjuk adni a tömböt, mert a struktúra tartalmazza mindkét adatot. Ha ezekből a tömbökből is több van, illetve futás közben foglaljuk le őket, akkor magát a struktúrát is dinamikusan kell lefoglalnunk.
DinTomb *din_tomb_foglal(int meret) { DinTomb *uj=(DinTomb *) malloc(sizeof(DinTomb)); uj->meret=meret; uj->adat=(double *) malloc(meret*sizeof(double)); return uj; } DinTomb *szamok=din_tomb_foglal(30); szamok->adat[15]=3.14;
Ha szeretnénk egy ilyen tömböt felszabadítani, akkor pedig két free()
hívásra van szükség. Egy malloc, egy free. És általában fordított sorrendben.
void din_tomb_free(DinTomb *dt) { free(dt->adat); /* a double tomb */ free(dt); /* es a struktura */ }
Idáig tartott a kulturált megoldás.
Tartalom
1 A hackelés
Innentől jön a hackelés. Tudjuk azt, hogy a struktúrában az adattagok egymás után helyezkednek el. Ha nem dinamikusan, hanem statikusan adjuk meg a tömb méretét, akkor kb. így néz ki a memóriatérkép:
typedef struct { int meret; double adat[4]; } DinTomb;
meret (int) |
adat[0] (double) |
adat[1] (double) |
adat[2] (double) |
adat[3] (double) |
Ilyenkor az egyes tömbelemek, a tárolt számok is benne vannak magában a struktúrában.
Ha egy ilyen struktúrányi helyet foglalunk dinamikusan, akkor értelemszerűen azoknak
is le lesz foglalva a helye. Egy malloc()
hívás kell majd csak, amihez
egy free()
tartozik, és ezért nem kell, vagy legalábbis nem érdemes
külön függvényeket írni a foglalásra és a felszabadításra.
Kérdés az, hogy meg lehet-e ezt csinálni úgy is, hogy dinamikus legyen az adat[]
tömb mérete. Márpedig meg lehet. Ha nagyobb területet foglalunk, akkor „túlindexelhetjük” a
tömböt: jogosan, hiszen tudjuk, hogy nagyobb a terület. Definiáljuk ezért a struktúrát úgy, hogy
a tömb egyetlen egy elemű, és foglaljuk le a következő módon:
typedef struct { int meret; double adat[1]; /* egy elemű tömb */ } DinTomb; DinTomb *uj=(DinTomb *) malloc(sizeof(DinTomb)+sizeof(double)*(meret-1)); uj->meret=meret; return uj;
A sizeof(DinTomb)
megadja a deklarált struktúra méretét; amelybe beleférnek a
meret
és az adat[1]
adattagok. Vagyis a méret, és legalább egy számnak
hely. Ha ehhez hozzáadjuk a további számok méretét (meret-1
), akkor egy akkora
memóriaterületet kapunk, amibe befér minden. A tömböt emiatt indexelhetjük 0-nál nagyobb számmal
is. A fordító nem ellenőrzi a túlindexelést; mi pedig tudjuk, hogy pl. az adat[3]
kifejezés hatására egy olyan double
számra kapunk hivatkozást, amelyik még a
lefoglalt memóriaterülethez tartozik. Nyilván, mert a tömbök elemei szorosan egymás mellett
helyezkednek el. Felszabadítani ezt egyetlen egy free()
hívással lehet, hiszen csak
egy malloc()
volt.
A dolog még egyszerűbb lenne, ha a C megengedné, hogy 0 méretű tömböt hozzunk létre: double adat[0]
,
ilyenkor a memóriaterület méretének kiszámolásába sem kellene a -1. Amit amúgy igazából nyugodtan elhagyhatunk,
mert 1 double
-nyi memória nem a világ. De a 0 méretű tömböt nem engedi.
2 A hackelés C99-ben
A C99 megengedi azt, hogy definiálatlan méretű tömböt hozzunk létre (flexible array member), de csakis egy struktúrában, csakis a struktúra végén (utolsó tagjaként), csakis egy darabot. Vagyis a következő kódrészlet C99-ben legális:
#include <stdlib.h> #include <stdio.h> typedef struct { int meret; double adat[]; /* definiálatlan méretű, csak 1, csak a végén */ } DinTomb; int main() { DinTomb *tomb; tomb = (DinTomb *) malloc(sizeof(DinTomb) + 10*sizeof(double)); tomb->meret = 10; for (int i=0; i<tomb->meret; ++i) tomb->adat[i] = i; for (int i=0; i<tomb->meret; ++i) printf("tomb[%d]=%f\n", i, tomb->adat[i]); free(tomb); return 0; }
Egy ilyen struktúrának egyébként csak dinamikusan foglalva van értelme; különben az
adat
tömb nulla méretű. Ezt ki is használjuk a méret kiszámításánál:
a sizeof(DinTomb)
értékéhez pont annyit kell hozzáadni, amekkora tömböt
a struktúra végére szeretnénk biggyeszteni, se többet, se kevesebbet.
Ezek olyan nyelvi elemek, amelyeket már külön engedélyezni kell a Code::Blocks beállításai
között; az ahhoz járó GCC fordító -std=c99
argumentummal fordítja csak le a fenti programot.