Adventi naptár
Czirkos Zoltán · 2021.08.24.
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.
Innentől jön a hackelés. Tudjuk azt, hogy a struktúrában az adattagok egymás után helyezkednek el (esetleg lehet közöttük néhány kitöltő (padding) bájt). 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.
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(void) {
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.