Memóriakezelés, dinamikus memóriakezelés

Hasonló dokumentumok
Programozási nyelvek Java

OOP #14 (referencia-elv)

Programozási nyelvek Java

Osztályok. 4. gyakorlat

Alprogramok, paraméterátadás

Programozás II. 3. gyakorlat Objektum Orientáltság C++-ban

A C programozási nyelv V. Struktúra Dinamikus memóriakezelés

1. Alapok. Programozás II

Programozási Nyelvek: C++

Programozás II. 4. Dr. Iványi Péter

C++ programozási nyelv Konstruktorok-destruktorok

Programozás C++ -ban 2007/7

3. Osztályok II. Programozás II

8. gyakorlat Pointerek, dinamikus memóriakezelés

C++ programozási nyelv

C++ referencia. Izsó Tamás február 17. A C++ nyelvben nagyon sok félreértés van a referenciával kapcsolatban. A Legyakoribb hibák:

JAVA PROGRAMOZÁS 2.ELŐADÁS

Pénzügyi algoritmusok

Programozás módszertan

C programozás. 6 óra Függvények, függvényszerű makrók, globális és

A verem (stack) A verem egy olyan struktúra, aminek a tetejéről kivehetünk egy (vagy sorban több) elemet. A verem felhasználása

Bevezetés a C++ programozási nyelvbe

Visual C++ osztály készítése, adattagok, és metódusok, láthatóság, konstruktor, destruktor. Objektum létrehozása, használata, öröklés.

Mutatók és mutató-aritmetika C-ben március 19.

Objektumok inicializálása

Java és web programozás

C memóriakezelés. Mutató típusú változót egy típus és a változó neve elé írt csillag karakterrel hozhatjuk létre.

Függvények. Programozás I. Hatwágner F. Miklós november 16. Széchenyi István Egyetem, Gy r

1. Mi a fejállományok szerepe C és C++ nyelvben és hogyan használjuk őket? 2. Milyen alapvető változókat használhatunk a C és C++ nyelvben?

Programozás alapjai gyakorlat. 4. gyakorlat Konstansok, tömbök, stringek

Bevezetés a programozásba. 8. Előadás: Függvények 2.

Programozás alapjai C nyelv 5. gyakorlat. Írjunk ki fordítva! Írjunk ki fordítva! (3)

Programozás II gyakorlat. 6. Polimorfizmus

Programozás alapjai. 10. előadás

Algoritmusok és adatszerkezetek gyakorlat 07

Programozás II. 2. Dr. Iványi Péter

Smart Pointer koncepciója

Pénzügyi algoritmusok

Bevezetés a C++ programozási nyelvbe

5. Gyakorlat. struct diak {

A programozás alapjai 1 Rekurzió

C programozási nyelv Pointerek, tömbök, pointer aritmetika

Java V. Osztályszint. lyszintű ű tagok. Példányváltozó. Osztályváltozó. Általános Informatikai Tanszék Utolsó módosítás:

Programozás alapjai C nyelv 7. gyakorlat. Függvények. Függvények(2)

Függvények. Programozás alapjai C nyelv 7. gyakorlat. LNKO függvény. Függvények(2) LNKO függvény (2) LNKO függvény (3)

Bevezetés a Python programozási nyelvbe

Programozás alapjai C nyelv 8. gyakorlat. Mutatók és címek (ism.) Indirekció (ism)

Mutatók és címek (ism.) Programozás alapjai C nyelv 8. gyakorlat. Indirekció (ism) Néhány dolog érthetőbb (ism.) Változók a memóriában

Bevezetés, a C++ osztályok. Pere László

Programozás I. 3. gyakorlat. Szegedi Tudományegyetem Természettudományi és Informatikai Kar

Bevezetés a programozásba I 10. gyakorlat. C++: alprogramok deklarációja és paraméterátadása

Programozás BMEKOKAA146. Dr. Bécsi Tamás 5. előadás

Memóriagazdálkodás. Kódgenerálás. Kódoptimalizálás

ISA szimulátor objektum-orientált modell (C++)

Programozás C++ -ban

Kivételkezelés, beágyazott osztályok. Nyolcadik gyakorlat

Pelda öröklődésre: import java.io.*; import java.text.*; import java.util.*; import extra.*;

Globális operátor overloading

A PROGAMOZÁS ALAPJAI 1. Függvény mint függvény paramétere. Függvény mint függvény paramétere. Függvény mint függvény paramétere

Bevezetés a programozásba II. 5. Előadás: Másoló konstruktor, túlterhelés, operátorok

Bevezetés a programozásba Előadás: A const

Programozás alapjai II. (4. ea) C++

Programozási nyelvek JAVA EA+GY 1. gyakolat

Bevezetés a programozásba. 9. Előadás: Rekordok

Programozás. (GKxB_INTM021) Dr. Hatwágner F. Miklós április 4. Széchenyi István Egyetem, Gy r

Java III. I I. Osztálydefiníció (Bevezetés)

Emlékeztető: a fordítás lépései. Szimbólumtábla-kezelés. Információáramlás. Információáramlás. Információáramlás.

Programozás C++ -ban

Programozás II gyakorlat. 8. Operátor túlterhelés

Programozási nyelvek a közoktatásban alapfogalmak II. előadás

Programozás C++ -ban

C++ Gyakorlat jegyzet 5. óra. A C++ szabvány több memóriatípust különít el. Ezek közül elsősorban a stack-et használtuk eddig.

Java II. I A Java programozási nyelv alapelemei

Programozás C és C++ -ban

Programozás C nyelven FELÜLNÉZETBŐL elhullatott MORZSÁK. Sapientia EMTE

500. AA Megoldó Alfréd AA 500.

OBJEKTUM ORIENTÁLT PROGRAMOZÁS JAVA NYELVEN. vizsgatételek

Bevezetés a programozásba I.

Alkalmazott modul: Programozás 8. előadás. Strukturált programozás: dinamikus memóriakezelés. Dinamikus memóriakezelés. Dinamikus memóriakezelés

Osztály és objektum fogalma

Elemi adatszerkezetek

Java III. I I. Osztálydefiníció (Bevezetés)

C# osztálydeníció. Krizsán Zoltán 1. .net C# technológiák tananyag objektum orientált programozás tananyag

Programozás I gyakorlat

Programozas 1. Strukturak, mutatok

C++ Standard Template Library (STL)

Java programozási nyelv 4. rész Osztályok II.

OOP: Java 8.Gy: Abstract osztályok, interfészek

Programozás II. 2. gyakorlat Áttérés C-ről C++-ra

Objektumelvű programozás

Struktúrák (struct) A struktúra szerkezetét meghatározó deklaráció általános formája:

C++ programozási nyelv Konstruktorok Gyakorlat

Kivételkezelés a C++ nyelvben Bevezetés

C++ Gyakorlat jegyzet 5. óra. A C++ szabvány több memóriatípust különít el. Ezek közül elsősorban a stack-et használtuk eddig.

1000.AA Megoldo Alfréd 1000.A

Kivételek, kivételkezelés a C++ nyelvben

A C programozási nyelv II. Utasítások. A függvény.

Programozás. (GKxB_INTM021) Dr. Hatwágner F. Miklós március 3. Széchenyi István Egyetem, Gy r

C vagy C++? Programozási Nyelvek és Fordítóprogramok Tanszék. Pataki Norbert. Programozási Nyelvek I.

A programozás alapjai

Átírás:

Metódus: függvények és eljárások összefoglaló neve. Memóriakezelés, dinamikus memóriakezelés Nézzük végig, a C++ memóriakezelését. Alapvetően három fő memóriaterületet különböztetünk meg. Végrehajtási verem (execution stack): elemei az aktivációs rekordok, az éppen futó metódusok találhatóak meg benne, legelső a main metódus, utána következik egy a main függvényben meghívott metódus. A program metódushívások láncolata. Az aktivációs rekordban találhatóak egy függvény paraméterei, lokális változói is. A statikus tömbök is a veremben kapnak helyet. Az itt található változók, paraméterek számára fordítási időben megtörténik a helyfoglalás. Statikus objektumok is az aktivációs rekordban kapnak helyet. Az éppen futó metódusra egy stack pointer mutat, metódusból való visszalépés esetén mindig ez a pointer ugrik az aktuális metódusra. Az aktivációs rekordokban található változókat szokás automatikus változóknak is hívni, hiszen automatikus létrejönnek, mikor a vezérlés eléri az őket tartalmazó blokkot és automatikusan megszűnnek, mikor elhagyjuk az őket tartalmazó blokkot. Dinamikus memória (heap): dinamikus tömbök, objektumok kapnak itt helyet. A heap-en futás időben foglalunk memóriát (allokálunk) new kulcsszó segítségével. A program futás közben az operációs rendszertől kér memóriát a dinamikus területen, az operációs rendszer pedig biztosítja a szükséges helyet, amennyiben még van szabad hely, egyéb esetben hibát kapunk. C++ esetén egy pointert kapunk, ami arra a memória területre mutat, ahol a dinamikus memória területen foglalt hely található, ha nem sikerül a helyfoglalás, akkor null pointert kapunk. A dinamikus memórián foglalt helyek és az ott tárolt értékek nem automatikusak, felszabadításukról gondoskodni kell (deallokáció). Bizonyos nyelvek, pl. Java, beépített mechanizmussal rendelkeznek, melyek detektálják és felszabadítják a használaton kívüli (elérhetetlen) objektumokat. Java-ban ez a garbage collector, pl.: mark-and-sweep garbage collector. C++ esetén a dinamikusan foglalt memória felszabadításáról a programozónak kell gondoskodni, delete kulszó. Ha nem gondoskodunk a dinamikusan foglalt memória felszabadításáról, akkor memória szivárgás (memory leak) történik, azaz a program befejezése után is foglalt marad az adott memóriaterület és elérhetetlenné válik. Statikus terület: speciális rész, a globális változók, statikus adattagok számára. 1

#include <iostream> int main() { int* p = new int(5); std::cout << p << : << *p << std::endl; *p = 3; std::cout << p << : << *p << std::endl; delete p; return 0; Dinamikus tömb Olyan tömb, aminek a mérete futás idejű adat, a heap-en kap helyet. A megfelelő helyen gondoskodnunk kell a tömb felszabadításáról is. Dinamikus tömb segítségével valósítható meg pl. a vektor adatszerkezet is. #include <iostream> int main() { int n; std::cin >> n; int* t = new int[n]; //új n elemű tömb létrehozása a heap-en, futás időben. delete[] t; //t tömb felszabadítása return 0; Karakter konverzió Feladat: szeretnénk karaktereket átkonvertálni más karakterekké, pl. valami egyszerű titkosítás. Van egy 256 elemű karakter tömbünk, benne mindenféle karakter, azt szeretnénk, hogy a konvertálandó karakter kódjának megfelelő indexű elem legyen a konvertálás eredménye. Három különféle megoldást nézünk, a szempontok: gyorsaság, kevés memóriahasználat, karbantarthatóság. 2

char conv (char ch) { static const char cnv[] = { z, y, *, x, w,. a return cnv[ch&0xff]; A függvényben egy lokális statikus konstans tömböt használunk, ez azt jelenti, hogy a tömb létrejön, amikor a vezérlés eléri a függvényt és végig a memóriában is marad, nekem minden hívás során létrehozni. A karakterek is számkánt vannak kezelve, ezért ch segítségével indexelhetjük a tömböt. A probléma azonban az, hogy ha 255-nél nagyobb ch értéke, akkor kiindexelünk a 256 elemű tömbből. A ch&0xff ch&255 kifejezés garantálja, hogy az index biztosan a [0, 255] intervallumba esik. A kifejezést célszerű hexadecimális formában írni, hiszen jobban kifejezi a célt. A & bitenkénti és műveletet jelent, nézzünk erre egy példát: 1 0 1 0 0 1 1 1 0 1 0 0 1 1 1 1 1 1 1 1 0 0 1 0 0 1 1 1 0 1 Az első 8 biten tartalmilag visszakapjuk az eredeti számot, felsőbb helyi értékeken csupa 0 bit marad, ezért ha az eredeti szám sem lehet nagyobb 255-nél, mivel a felsőbb helyi értékeken található 1-es bitek az és művelet miatt 0-ák lesznek. A függvényben használ 256 elemű tömböt pl. script segítségével is fel lehet tölteni. A megoldás hátránya a relatív nagy memória fogyasztás. A kevés memóriahasználatot szem előtt tartva: cahr conv(char ch) { switch(ch) { case A : return t ; break; case G : return x ; break; case m return a ; break; default: return c ; Ebben az esetben hatékonyabb a switch szerkezet használata, mint else-if-eket használni. Ha diszjunkt case ágakat szeretnénk, akkor szükséges a break használata, de itt mivel minden case-ág return kulcsszóval zárul elhagyható lenne. 3

Karbantarthatóság A karbantarthatóságot szem előtt tartva készíthetünk két tömböt, az egyik a konvertálandó karaktereket tartalmazza, a másik az eredményeket. Ennél a megoldásnál hiba lehet, ha a két tömb mérete nem egyezik meg, csak futás időben derülne ki. Tovább javítva ezt a megoldást, használjunk egy tömböt, amiben saját típusú elemeket tárolunk. struct Pair { ; char from; char to; Itt az lehet probléma, hogy ha nem adtuk meg vagy a from vagy a to értékét. Ezt a hibát úgy védhetjük ki, ha létrehozunk egy olyan függvényt, ami biztosítja hogy csak akkor jön létre a Pair típusú objektum, ha mindkét paramétert megadtuk. Ez a függvény lesz a konstruktor, konstruktort struktúrához is írhatunk. struct Pair { ; char from; char to; Pair(char from, char to) { this->from = from; //this a saját objektumra mutató pointer, rajta keresztül tud az objektum a saját helyéről this->to = to; C++-ban a struktúrából is objektum fog képződni, jelen esetünkben, statikus formában a fordító hozza létre és a végrehajtási veremben lesz tárolva. A class és a struct között az alapértelmezett különbség az, hogy ha nem adunk meg a láthatóságra vonatkozó információkat, akkor a struct tagjai alapértelmezetten publikusak, míg a class-é privátok. A fő különbség inkább a használat céljában van. Az osztályok az OOP alapkövei, logikailag összetartozó adatokat és rajtuk értelmezett műveleteket fognak össze. A struktúrák inkább csomagoló szerepet látnak el, összefoghatnak összetartozó dolgokat, adatokat, pl. ha sok mindent kell átadni függvény paraméterként, stb. Pair p; //fordítási hiba lenne, mivel nem rendelkezik paraméter nélküli konstruktorral. Pair p( a, b ); //fordítási időben fog létrejönni a veremben. 4

Referenciák Alapvetően álnevet jelent. A fogalom nagyon hasonlít a pointerekre, a működésük a háttérben pointerekkel van megoldva. A referencia a C nyelvben nem ismert fogalom. A fő különbségek Pointer (C, C++), null pointer változhat, hogy hova mutat nem kötelező inicializálni Referencia (C++) nincs null referencia, mindenképpen hivatkoznia kell valamire mindig ugyanannak az álneve kötelező inicializálni pointer aritmetika - int x = 2; int y = 5; int* p = &x; int& r = x; //innentől r és x elválaszthatatlanok, r x álneve r = 4; *p = 5; p = &y; //OK r = y; //r nem y álneve lesz Az fenti kódrészletben az x változóra tulajdonképpen három néven hivatkozhatunk, értékét p pointer és r referencia segítségével is megváltoztathatjuk. p azonban mutathat máshova is, de r már nem. int& z; //fordítási hiba 5

Függvény paraméterek Érték szerinti paraméter átadás void f(int a, int b) { a = b; int x = 1; int y = 2; f(x, y); std::cout << x <<, << y << std::endl; Mit ír ki a fenti kódrészlet? 1, 2 lesz az eredmény, hiába adtuk értékül az eljárásban a-nak b- t az eredeti változókra ez nem lesz hatással. Az érték szerinti paraméterátadás lényege, hogy a függvény formális paraméterei (jelen esetben a és b) és a függvény aktivációs rekordjában kapnak helyet, tulajdonképpen lokális változók lesznek. Mikor meghívjuk a függvényt, akkor az aktuális paraméterek értékei (itt x, y) belemásolódnak ezekbe a lokális változókba és az ő értékük cserélődik meg. A csere x és y változókra tehát nem lesz hatással. 6

Referencia szerinti void f(int& a, int& b) { a = b; int x = 1; int y = 2; f(x, y); Itt x és y értéke valóban fel fog cserélődni. A referencia szerint paraméterátadás azt jelenti, hogy az f függvény a és b formális paraméterei tulajdonképpen a függvény hívásakor átadott aktuális paraméterek álnevei lesznek. Durván fogalmazva a függvény a változók memória címét használja. Az érték szerinti paraméterátadás hátránya, hogy feleslegesen sok memóriát foglalunk, pl. ha egy nagyméretű objektumot, vagy vektort adunk át akkor az is le fog másolódni. A referencia szerinti paraméterátadás esetén ez a probléma nem áll fenn, mivel ott ugyanarra a változóra hivatkozunk álnéven. De mi van akkor, ha biztosítani szeretnénk, hogy a függvény ne változtathassa meg az eredeti változók értékét, mégis el szeretnénk kerülni a felesleges másolási költségeket? 7

Konstans referencia szerinti void f(const int& a, const int& b) { a = b; //fordítási hiba Ebben az esetben is referencia szerinti hivatkozás történik, de a függvény nem változtathatja meg a paramétereket, ezt a fordító ellenőrzi. Mi a helyzet a tömbökkel? Korábban már láttuk, hogy a tömbök első elemre mutató pointerként adódnák át egy függvénynek, tehát semmi esetben sem másolódnak le, de a pointer is érték szerint adódik át. Nézzünk néhány példát: void g(int& r) { void h(const int& r) { int x = 4; g(x); //OK g(2); //fordítási hiba, számkonstansra nem tudunk referenciát állítani g(x + 3); //fordítási hiba //x + 3 egy temporális érték lesz, nem x értéke változik meg, ezért erre sem tudunk referenciát állítani g(x++); //fordítási hiba, hiszen itt csak x értékét olvassuk ki és adjuk át, temporális érték g(++x); //OK, itt csak megnöveljük x értékét egy-el mielőtt átadjuk h(x); //OK h(x + 4) //OK, konstans referenciával mutathatunk a temporális értékre h(5); //OK h(x++); //OK h(++x); //OK 8

Függvény pointer, függvény referencia Előfordulhat, hogy egy függvénybe valamilyen tevékenységet szeretnénk bejuttatni, egyszóval egy mási függvényt, de nem fixen meghívni, hanem paraméterként adjuk át. Pl.: van egy összegző függvényünk, de azt szeretnénk, hogy tudjunk négyzetszámokat vagy reciprokokat is összegezni és ehhez ne kelljen minden újra megírni, csak a legszükségesebbeket. double sum(double (*a) (int), int n) { double sum = 0; for (int i = 1; i <= n; ++i) { sum += a(i); return sum; double sqr(int x) { return x * x; sum(sqr, 5); Függvény referencia double sum(double a(int), int n) { A függvények is a memóriában kapnak helyet, pointerekkel, referenciákkal hivatkozhatunk rájuk. C++-os inline függvények esetén ez nem működik, de inline függvénynek is lehet függvény paramétere. Függvény pointer esetén megadjuk a függvény visszatérési értékének típusát, a nevét, amivel az adott függvényben hivatkozunk és, paraméter(einek) típusát, formális paraméter nevet nem adunk. C, C++ esetén is működik. A függvény referencia csak C++ esetén működik. 9

Bináris keresőfa Készítsünk egy egyszerűsített bináris keresőfát. A segítségével írjuk ki növekvő sorrendben a standard input-ról érkező számokat. struct Node { ; Node* left; Node* right; int val; Node(const int& val) { left = right = 0; this->val = val; void print(const Node* n) { if (n) { print(n->left); std::cout << n->val << ; print(n->right); void insert(node*& n, int v) { if (n) { else { insert(v <= n->val? n->left : n->right, v); n = new Node(v); 10

void dealloc(node* n) { if (n) { int main() { dealloc(n->left); dealloc(n->right); delete n; Node* root = 0; int i; while (std::cin >> i) { insert(root, i); print(root); dealloc(root); return 0; Az egyszerű bináris keresőfában rendezetten tárolódnak az értékek, minden csúcsra igaz, hogy a nála kisebb értékű elemek tőle balra, a nála nagyobbak tőle jobbra helyezkednek el. A kiíráshoz rekurzív inorder fabejárást alkalmazunk, így növekvő sorrendben kapjuk az eredményt. Az inorder bejárás lényege, hogy minden csúcs esetén először a bal részfát járjuk be, aztán feldolgozzuk az aktuális csúcsot (itt kiírjuk az értékét), végül a jobb részfát dolgozzuk fel. 11

Ha az insert metódusnál lehagyjuk a referenciát és csak sima pointer a paraméter, akkor nem lesz hiba, de a fa nem fog felépülni, hiszen érték szerint adtuk át a pointert, a függvényben belül ugyan megváltozik az értéke, de ez nem lesz hatással az eredeti változóra. Mivel a csúcsokat dinamikusan hoztuk létre, gondoskodnunk kell a felszabadításukról is. Ezt a dealloc eljárás biztosítja postorder fabejárás formájában. A postorder bejárás során először feldolgozzuk egy csúcs bal- és jobboldali részfáit végül pedig az aktuális csúcsot. A felszabadítás során csak így járhatunk el, hiszen ha felszabadítanánk az aktuális csúcsot, mielőtt elrendeztük volna a gyerekeit, akkor elveszítjük az elérést a gyerekeire. 12