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.
