Kihagyás

A memória felosztása

Kis áttekintés

Ahhoz, hogy jobban megértsük, az egyes adatokat fizikailag hogyan is tárolja a programunk, és miért és hogyan tudunk adott esetben optimálisabb kódot írni C-ben, ha az adatokkal ügyesen bánunk, meg kell ismerjük azt, hogy egy program hogyan foglal helyet a memóriában.

Memória felépítése

Az alábbi kép mutatja, hogyan épül fel általánosságban egy C program memóriája. x86-on a verem (stack) a magasabb memóriacímek felől nő az alacsonyabb címek felé (ahogy az ábra is mutatja), ám ez nem szabvány, más architektúrákon fordítva lehet.

memory

A memóriaterületekről néhány szó:

  • A stacken (veremben) tárolódik egy-egy függvényhívás adata, vagyis (egyebek mellett) a lokális változók, a függvényeknek átadott paraméterek. A felszabadítás automatikusan, a meghívott függvényből való visszatéréskor megtörténik (emiatt veszélyes egy függvény lokális változójának memóriacímét visszaadni, hiszen az a terület automatikusan felszabadul).
  • A heap-en/dinamikus memóriában tárolódik mindaz, amit dinamikusan (new, malloc, calloc, ...) foglaltunk le. A heap általában az alacsonyabb címek felől növekszik a magasabb címek felé. Ennek felszabadítása nem automatikus, a programozónak kell nem elfelejtenie (delete, free). Ezt a memóriaterületet használják a dinamikusan betöltött library-k is. Elérése költségesebb, mint verem esetén, de a mérete nagyobb, illetve átméretezhető.
  • BSS (Block Started by Symbol, Uninitalized Data Segment): olyan globális és statikus változók kerülnek ide, amelyeket a programozó nem inicializált saját maga, ezért a program indulásakor automatikusan 0 értéket kapnak. Például a globális változók vagy a függvények static kulcsszóval ellátott változói.
  • DS (Initalized Data Segment): olyan globális és statikus változók, amiket a programozó ellátott kezdőértékkel.
  • Text: a programból készült gépi utasítások helye.
  • Environment: parancssori argumentumok, környezeti változók helye.

A memóriának ezeket a területeit szegmenseknek nevezzük. Minden szegmens meghatározott szabályokkal rendelkezik. Például, hogy az adott szegmens adatai írhatóak-e, vagy sem! Ha az adott szegmens szabályait megsértjük, kapjuk az úgynevezett szegmentálási hibát, vagy segfaultot, ami a programok talán leggyakoribb abortálását okozza.

Tip

A size programot ha alkalmazzuk a binárisra, amit futtatunk, megtudhatjuk, hogy az egyes szegmensekre mekkora terület jut.

Változók és memória

A számítógép fenti memória modelljét ezután úgy képzeljük el, mintha rekeszekre bontanánk, ahol minden egyes rekesz 1 byte adatot képes tárolni. (Tulajdonképpen a C-ben mind a kód részre, mind az adat részre tudunk így tekinteni.) Minden rekeszhez egy egyedi sorszám tartozik, amit memóriacímnek hívunk. Ez a cím határozza meg, hogy egy adott adat pontosan hol található a memóriában.

Amikor egy adott típusú változót használunk a programban, akkor valójában azt mondjuk meg, hogy a memóriának egy hány bájtból álló darabját szeretnénk használni. (A változó egyéb jellemzői, illetve létrehozásának módja megadja, hogy mely memória szegmensben kerül tárolásra.) Például egy int típusú változó általában 4 byte-on tárolódik, tehát négy egymás melletti memóriacímre lesz szükség.

A változók memóriában való tárolása tehát két fontos információn alapszik:

  • a kezdőcím (az első byte címe, ahol az adat tárolódik),
  • és az adattípus mérete (hány byte-on terül el az adat).

Ez alapján a fordítóprogram pontosan tudja, hogy hogyan értelmezze az adott memóriaterület tartalmát – vagyis hogyan olvassa ki vagy módosítsa az ott tárolt értéket.

Tegyük fel, hogy ezt írjuk a programba:

1
int x = 5;

Ez mit jelent a memóriában?

  1. A x nevű változónk int típusú, tehát (a legtöbb rendszeren) 4 byte-ot foglal.
  2. A fordító lefoglal neki egy szabad memóriaterületet (például a 0x1000 címről kezdődően).
  3. A változó értékét, azaz a 5-öt bináris formában eltárolja ezen a 4 byte-os memóriaterületen.

Memóriakép:

1
2
3
4
5
6
(hex)   Tartalom (hex)
---------------------------
0x1000      05
0x1001      00
0x1002      00
0x1003      00

(Tegyük fel, hogy kis endian a rendszer, vagyis a legkisebb helyiérték kerül a legkisebb címre.)

Magyarázat:

  • A 5 decimális szám binárisan: 0000 0000 0000 0000 0000 0000 0000 0101
  • Ez hexadecimálisan: 0x00000005
  • Ez a 4 byte a 0x1000 címtől kezdve fordított sorrendben kerül tárolásra a kis endian miatt:
  • 0x1000: 0x05 (legalacsonyabb byte)
  • 0x1001: 0x00
  • 0x1002: 0x00
  • 0x1003: 0x00