Adventi naptár

Czirkos Zoltán · 2021.08.24.

Adatrejtés C-ben

C-ben alapvetően egy struktúra teljesen átlátszó. Minden tagját ismerni lehet, mert látszik a definíciója. Ha nem látszik a definíció, akkor nem lehet létrehozni példányt belőle.

Vagy mégis. A C megengedi azt is, hogy egy már deklarált, de még definiálatlan típusú struktúrára mutató pointert hozzunk létre. Pontosan ez történik egy egészen egyszerű láncolt listánál is. A struktúrán belül a pointer megadásánál a struct Lista típus még ismeretlen: definiálatlan, hiszen éppen definiálás alatt van. Rá mutató pointert létrehozni viszont lehet (a következő láncszem pointere):

struct Lista {
    double szam;
    struct Lista *kovetkezo;
};

Ez lehetővé teszi azt, hogy egy struktúra adattagjait elrejtsük.

1. Átlátszatlan struktúrák

Legyen példa egy dinamikus tömb. A tömböt lehessen lefoglalni a megadott mérettel, lehessen felszabadítani, lehessen átméretezni. Lehessen lekérdezni az egyik elemét, és beállítani azt valamilyen értékre.

Ha dinamikusan foglalunk egy memóriaterületet, akkor egy pointert kaptunk. A pointer mellé minden esetben meg kell jegyeznünk a darabszámot is, hogy a tömb mennyi elemet tartalmaz. A lefoglalt memóriára mutató pointer és a darabszám két összetartozó adat. Külön nincs értelmük, illetve ha az egyik változik, változnia kell a másiknak is. Tegyük be ezért őket egy struktúrába. Tegyünk így azért is, hogy egy függvénynek könnyen át lehessen adni egy ilyen tömböt, egyetlen paraméterrel:

#include <stdio.h>
#include <stdlib.h>

typedef struct DinTomb {
    double *szamok;
    int meret;
} DinTomb;

DinTomb *dintomb_foglal(int meret);
void dintomb_free(DinTomb *dt);
void dintomb_kiir(DinTomb const *dt);

int main(void) {
    DinTomb *d;
    
    d = dintomb_foglal(50);
    d->szamok[0] = 5;
    dintomb_kiir(d);
    dintomb_free(d);
    
    return 0;
}

Ez így szerepelt előadáson is, és kiválóan működik. Probléma csak egy van: semmi akadálya nincs annak, hogy ezt a sort leírjuk:

d->meret = 3217;

Ilyenkor a dinamikus tömböt kezelő függvények megzavarodnak: az átméretező függvény helytelenül fogja átmásolni az új, átméretezett tömbbe az adatokat, mert rosszul fogja tudni a tömb előző méretét.

2. A megoldás

Hogy lehet ezt megoldani? Rejtsük el a struktúra belsejét a main() függvény elől. Deklaráljuk a struktúrát, de ne definiáljuk azt:

struct DinTomb;

Ha így teszünk, és a main() függvény ennyit lát csak, akkor nem fog tudni hivatkozni az adattagokra. Egyetlen dolgot tud majd tenni: a struktúrára mutató pointert létrehozni. (Ha a benne lévő mezőkre hivatkozna, akkor dereferencing pointer of incomplete type hibaüzenetet fog adni a fordító.)

Szóval az adattagokat nem látja, de a dintomb_foglal() függvénytől át tudja venni a lefoglalt struktúrára mutató pointert. Bármelyik másik függvénynek át is tudja adni azt. Ez egyébként nem ismeretlen dolog, ugyanígy működik a FILE típus. Ez általában egy struktúra. Nem tudjuk, mi van benne, de ennek ellenére el tudjuk érni a fájlokat a fájlkezelő függvényeken keresztül.

A tömbös példánkban is minden művelethez, amelyet a tömbön végzünk, egy függvényt kell írnunk. A függvények egy másik forrásfájlban lesznek, amely forrásfájl tetején nem csak deklaráljuk, hanem definiáljuk is a DinTomb struktúrát.

3. Mire jó ez az egész?

Nagyon egyszerű: elválaszthatjuk a program részeit egymástól. Ha rosszul működik a dinamikus tömbünk, akkor az azt kezelő függvényekben kell a hiba okát keresnünk. Mert más nem nyúlhatott a struktúrában lévő mezőkhöz. (Legalábbis ha helyesen kezeljük mindenhol a memóriát.)

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

/* ============ DINTOMB.H ============ */
typedef struct DinTomb DinTomb;                     /* csak deklaracio! */

DinTomb *dintomb_foglal(int meret);                 /* uj tombot foglal. */
DinTomb *dintomb_masolat(DinTomb const *eredeti);   /* uj tomb – mely masolat. */
void dintomb_free(DinTomb *dt);                     /* felszabaditja a tombot */

void dintomb_kiir(DinTomb const *dt);                  /* kiir minden szamot */
void dintomb_atmeretez(DinTomb *dt, int ujmeret);      /* atmeretezi; ha csokken, a
                                                          hatsok elvesznek, ha no,
                                                          az ujak memoriaszemet */
void dintomb_set(DinTomb *dt, int index, double adat); /* adott indexut beallit */
double dintomb_get(DinTomb const *dt, int index);      /* adott indexut lekerdez */

/* ============ MAIN.C ============ */
int main(void) {
    DinTomb *d, *dm;
    
    d = dintomb_foglal(50);
    
    dintomb_set(d, 15, 3.14);
    printf("d[15] = %g\n", dintomb_get(d, 15));
    
    printf("d = [");
    dintomb_kiir(d);
    printf("]\n");
    
    dm = dintomb_masolat(d);
    dintomb_free(d);
    
    printf("dm = [");
    dintomb_kiir(dm);
    printf("]\n");
    
    dintomb_free(dm);
    
    return 0;
}

/* ============ DINTOMB.C ============ */
/* #include "dintomb.h"  termeszetesen lenne az elejen */
struct DinTomb {               /* ez mar definicio is! */
    double *szamok;
    int meret;
};

DinTomb *dintomb_foglal(int meret) {
    DinTomb *uj;
    uj = (DinTomb *) malloc(sizeof(DinTomb));
    uj->meret = meret;
    uj->szamok = (double *) malloc(meret*sizeof(double));
    return uj;
}

void dintomb_free(DinTomb *dt) {
    free(dt->szamok);
    free(dt);
}

void dintomb_kiir(DinTomb const *dt) {
    for (int i = 0; i < dt->meret; ++i)
        printf("%g ", dt->szamok[i]);
}

DinTomb *dintomb_masolat(DinTomb const *eredeti) {
    DinTomb *uj = dintomb_foglal(eredeti->meret);
    uj->meret = eredeti->meret;
    for (int i = 0; i < eredeti->meret; i++)
        uj->szamok[i] = eredeti->szamok[i];
    return uj;
}

void dintomb_atmeretez(DinTomb *dt, int ujmeret) {
    if (dt->meret == ujmeret)
        return;
    
    /* uj double tombot foglalunk, es atmasoljuk bele */
    int kisebb = ujmeret<dt->meret?ujmeret:dt->meret;
    double *ujmemoria = (double *) malloc(ujmeret*sizeof(double));
    for (int i = 0; i < kisebb; ++i)
        ujmemoria[i] = dt->szamok[i];
    free(dt->szamok);
    /* uj adatok */
    dt->szamok = ujmemoria;
    dt->meret = ujmeret;
}

void dintomb_set(DinTomb *dt, int index, double adat) {
    /* indexhatar ellenorzes - megvan az info hozza */
    assert(index >= 0 && index<dt->meret);
    dt->szamok[index] = adat;
}

double dintomb_get(DinTomb const *dt, int index) {
    assert(index>=0 && index<dt->meret);        /* indexhatar */
    return dt->szamok[index];
}