A memóriák típusairól és a memóriakezelésről

Hasonló dokumentumok
8. gyakorlat Pointerek, dinamikus memóriakezelés

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

OOP #14 (referencia-elv)

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

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.

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

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

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

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:

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

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

Programozás C++ -ban

C++ programozási nyelv Konstruktorok-destruktorok

1. Alapok. Programozás II

3. Osztályok II. Programozás II

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

1. Template (sablon) 1.1. Függvénysablon Függvénysablon példányosítás Osztálysablon

Programozás I gyakorlat. 10. Stringek, mutatók

Programozás C++ -ban 2007/7

- 1 - Konstansok használata. Döntsük el, van-e fordítási idejű hiba az alábbi programrészletekben! a) const char * str="zh"; str[0]++;

Osztályok. 4. gyakorlat

Programozási nyelvek Java

Járműfedélzeti rendszerek II. 3. előadás Dr. Bécsi Tamás

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

10. gyakorlat. Pointerek Tárolási osztályok

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

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

C++ programozási nyelv Konstruktorok Gyakorlat

Statikus adattagok. Statikus adattag inicializálása. Speciális adattagok és tagfüggvények. Általános Informatikai Tanszék

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

Alprogramok, paraméterátadás

Alkalmazott modul: Programozás 8. előadás. Strukturált programozás: dinamikus memóriakezelés. Dinamikus memóriakezelés. Dinamikus memóriakezelé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.

C++ programozási nyelv

1.1. A forrásprogramok felépítése Nevek és kulcsszavak Alapvető típusok. C programozás 3

Szövegek C++ -ban, a string osztály

C string műveletek (string.h alkalmazása)

Programozas 1. Strukturak, mutatok

Pénzügyi algoritmusok

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

Programozás C++ -ban

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

Smart Pointer koncepciója

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.

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

1. Jelölje meg az összes igaz állítást a következők közül!

Programozás. Osztályok, Származtatott osztályok. Fodor Attila

Pénzügyi algoritmusok

Alkalmazott modul: Programozás 10. fejezet. Strukturált programozás: dinamikus memóriakezelés. Giachetta Roberto

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

Bevezetés a C++ programozási nyelvbe

Miről lesz ma szó? A PROGAMOZÁS ALAPJAI 1. Dinamikus változók. Dinamikus változók. Dinamikus változók. Dinamikus változók. 7.

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

Számítógép és programozás 2

Programozás C++ -ban

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

Bevezetés a Python programozási nyelvbe

Bevezetés a programozásba Előadás: Objektumszintű és osztályszintű elemek, hibakezelés

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

Programozási Nyelvek: C++

Objektumok inicializálása

A C programozási nyelv III. Pointerek és tömbök.

5. Gyakorlat. struct diak {

Java és web programozás

Programozás alapjai II. (9. ea) C++ többszörös öröklés, cast, perzisztencia

Programozás alapjai. (GKxB_INTM023) Dr. Hatwágner F. Miklós augusztus 29. Széchenyi István Egyetem, Gy r

5. gyakorlat. Konstansok Tömbök Stringek

Occam 1. Készítette: Szabó Éva

Programozás módszertan

A C programozási nyelv III. Pointerek és tömbök.

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

Programozás C- és Matlab nyelven C programozás kurzus BMEKOKAM603 Mutatók. Dr. Bécsi Tamás 7. Előadás

Programozás II gyakorlat. 7. Példák a polimorfizmus alkalmazásaira

Programozás II gyakorlat. 6. Polimorfizmus

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

Láncolt lista. az itt adott nevet csak a struct deklaráción belül használjuk

Tömbök kezelése. Példa: Vonalkód ellenőrzőjegyének kiszámítása

C++ programozási nyelv

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.

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

C++ programozási nyelv Struktúrák a C++ nyelvben

Készítette: Nagy Tibor István

Programozás C és C++ -ban

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

Programozás alapjai gyakorlat. 2. gyakorlat C alapok

A C# programozási nyelv alapjai

JAVA PROGRAMOZÁS 2.ELŐADÁS

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

Java és web programozás

Forráskód formázási szabályok

Adatbázis és szoftverfejlesztés elmélet

Bevezetés a programozásba II. 8. Előadás: Osztályok, objektumok, osztályszintű metódusok

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

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

10. gyakorlat Struktúrák, uniók, típusdefiníciók

7. fejezet: Mutatók és tömbök

HORVÁTH ZSÓFIA 1. Beadandó feladat (HOZSAAI.ELTE) ápr 7. 8-as csoport

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

Java II. I A Java programozási nyelv alapelemei

Átírás:

A memóriák típusairól és a memóriakezelésről A C és C++ programozás tanulása során sok problémát okoz a memóriakezelés megértése. Az alábbi magyarázattal próbálom a lényeges elemeket összefoglalni. czirkos@eet.bme.hu A memóriaterületek A program a futása során három jól elkülöníthető szerepű memóriaterülettel rendelkezik, ezek funkciók szerint a globális változók memóriaterületei, a verem és a dinamikusan lefoglalt memóriaterületek. A memóriakezelés megértése lényegében azon múlik, hogy az ember tisztában van e vele, a különféle módokon deklarált változók és adatterületek melyik helyre kerülnek. Abból már egyértelműen következnek az órákon általában bemutatott ökölszabályok is, például hogy miért nem lehet lokális változóra mutatót visszaadni egy függvényből. A globális memóriaterület A globális memóriaterületen helyezkednek el, ahogyan a neve is mutatja, a globális változók. Ezek a program egész futása alatt léteznek. Az ott létrehozott változók elhelyezkedése nem változik meg. A kiosztásuk már a program fordításakor eldől, és még azelőtt létrejönnek, hogy a main() függvény első sorát elkezdené végrehajtani a gép. 1. int a; 2. char s[100]="hello"; 3. char *ptr="szoveg"; 4. 5. int main( ) 6. 7. printf("%d", i); 8. A változók elhelyezését könnyű megérteni, ha betűről betűre ragaszkodik az ember ahhoz, amit a kódrészlet tartalmaz. A gép úgysem képes másra... Vastag betűvel kiemeltem, hogy melyik sorban mit, milyen típusú változót deklarálunk. Az 1. sorban egy integert (int) hozunk létre a globális memóriaterületen. A 2. sor egy száz elemű, karakterekből álló tömböt (char [100]) hoz létre. A tömb első 6 karakterét használjuk (az öt betűs hello és a lezáró nulla a sztring végén), a többi tartalék hely, hogy hosszabb sztringet is másolhassunk ide. A 3. sor cselesebb. Ott csak egy pointert deklarálunk (char *), amelyik egy, a globális

memóriaterületen elhelyezett névtelen karakter tömbre mutat 1. Ez nagyon fontos különbség az előzőhöz képest, ahol nem egy pointer és egy tömb jött létre, hanem csak egy tömb. Itt egy pointer is létrejön, amely igazából független a tömbtől, csak most éppenséggel kezdeti értékként ráállítottuk arra a tömbre. A 7. sorban a printf hívás hasonlít ehhez. Megadunk egy formátum sztringet, ami alapján tudja, hogy hogyan kell kiírnia a változót. Ez a formátum sztring is a globális memóriaterületen helyeződik el, ugyancsak névtelen tömbként. A printf egy pointert kap erre futás közben, ahogy a harmadik sor ptr je is csak egy mutató az előző névtelen tömb elejére. Ha a printf után azt mondanánk, hogy ptr=s; akkor onnantól kezdve ptr az s[] tömbre mutat. A szoveg sztringet akkor többé semmi módon nem érjük el, mivel névtelen tömb, és nem tudjuk, hol van a memóriában nem mutat rá pointerünk. A verem A verembe a függvények lokális változói kerülnek. A verem speciális tulajdonsága, hogy a tartalma fel le változik; ha egy függvény belsejének végrehajtásába kezdünk, akkor a verem tetején létrejönnek a függvény lokális változói, ha pedig a függvényből visszatérünk, akkor azok a változók megszűnnek. Az adott függvényhíváshoz tartozó memóriaterületet a veremben stack frame nek nevezzük. A veremben minden függvény csak a saját lokális változóit látja. Ha a függvény saját magát hívja meg, akkor különböző és egymástól független példányok keletkeznek a lokális változóiból. 1. void fv(int b) 2. 3. char *ptr="global"; 4. char tomb[]="ding"; 5. b=6; 6. 7. 8. int main( ) 9. 10. int a=5; 11. char s[50]="verembe"; 12. 13. fv(a); 14. Ennél a példánál a következő módon alakul a memóriaterületek tartalma. A program indításkor a main() függvényt kezdi el végrehajtani. A main függvénynek két lokális változója van, egy integer (int a, 10. sor), és egy ötven elemű karakter tömb (11. sor). A karakter tömb maga, vagyis az egyes karakterek is a veremben helyezkednek el. 1 Ennek a karakter tömbnek a típusa C++-ban const char[], vagyis konstans memóriaterületre kerül. Így aztán strcpy(s, "asdf") működik, strcpy(ptr, "asdf") pedig nem. Emiatt a 3. sorra egyébként minden valamire való fordító figyelmeztetést is küld.

Ha meghívjuk az fv() függvényt (13. sor), akkor az induláskor létrejönnek a veremben a paraméterei (!) és lokális változói, b nevű egész (1. sor), a ptr nevű pointer (3. sor) és a tomb nevű tömb (4. sor). A ptr rel megint csak egy pointert deklarálunk, nem pedig egy tömböt! Ha char * ot írunk, a gép char * ot ért alatta. A verembe, amely a lokális változókat tárolja, így csak a pointer kerül, amely be is állítódik a global szót tartalmazó tömbre. A tömb az előzőekhez hasonlóan a globális memóriaterületre került, és a program egész futása alatt létezik. Az 4. sorban nem pointert, hanem egy tömböt deklarálunk; annak tartalma is a verembe kerül, még így is, hogy méretét nem adtuk meg, hanem az inicializáló sztring alapján számolja a fordító. A deklaráció pontos értelmezése mindent eldönt! A szürkével jelölt változók csak addig léteznek, amíg a függvény belsejében vagyunk, a 6. sorig bezárólag. Ha visszatérünk a függvényből, és újra a main() ben vagyunk, akkor már nem. A b=6 emiatt értelemszerűen nem a main() változóját, a t módosítja, hanem a veremben lévő másolatot. A heap A dinamikus memóriaterület, vagyis a heap olyan terület, amelyből egy adott nagyságú részt a program futása közben kérhetünk, és ha már nem kell, visszaadhatjuk. Így foglalhatunk le akkora méretű memóriát, amelynek a nagyságát a program írása, fordítása közben még nem ismerjük. A lefoglaláskor egy pointert, vagyis egy mutatót kapunk arra a memóriahelyre, ahol a gép megfelelő nagyságú területet talált; amikor arra már nincs szükségünk, akkor felszabadítjuk azt. A terület a lefoglalástól kezdve mienk, egészen addig, amíg vissza nem adjuk. C ben ez a malloc() és free() függvényhívásokkal, C++ ban a new és a delete operátorokkal történik 2. A következő példa kód 3. sorban először is deklarálunk egy char * típusú pointert. A pointer maga a veremben jön létre, és beállítjuk egy dinamikusan lefoglalt memóriaterületre, amely száz karaktert képes tárolni. C ben ehhez a malloc() függvényhívást használjuk, C++ ban pedig a new[] operátort. A száz karakternek való hely a dinamikus memóriaterületen foglalódik le, vagyis a heapen. A 4. sorban egy Komplex számra mutató pointert deklarálunk, és foglalunk helyet a heapből egyetlen egy Komplex számnak. Az 5. sor pedig megint csak egy pointert deklarál csupán a veremben; a pointernek akkor lesz értelme, ha beállítjuk, hogy valahova mutasson, valami számunkra hasznos helyre. Jelen esetben egy nagy, ezer egész számot tartalmazó, dinamikusan foglalt tömbre. A 7. sorban a lefoglalt karakter tömbbe másolunk egy sztringet. A sztring hossza 11 betű, meg van még egy lezáró nullánk, vagyis 12 karakterből áll; 100 karaktert foglaltunk, vagyis ez rendben van. Az eredeti sztring egyébként az előző példákhoz hasonlóan a globális memóriaterületen van, névtelenül. Onnan másolódik át most a heapen lefoglalt területre. 2 A C++ szabvány megkülönbözteti ezt a kettőt. A malloc-free páros által használt memóriaterület neve ott heap, a new-delete operátorok által használt pedig free store. Ezek persze lehetnek közösek; és egy programban lehet használni egyszerre mind a kettőt. Csak amit a malloc() foglalt, azt free()-vel kell felszabadítani, nem delete-tel. Ugyanez igaz fordítva is.

C ben: 1. int main() 2. 3. char *tea=malloc(100*sizeof(char)); 4. Komplex *k=malloc(sizeof(komplex)); 5. int *sok=malloc(1000*sizeof(int)); 6. 7. strcpy(tea, "bai ji guan"); 8. free(k); 9. 10. free(tea); 11. free(sok); 12. C++ ban: 1. int main() 2. 3. char *tea=new char[100]; 4. Komplex *k=new Komplex; 5. int *sok=new int[1000]; 6. 7. strcpy(tea, "bai ji guan"); 8. delete k; 9. 10. delete[] tea; 11. delete[] sok; 12. A 8. sorban felszabadítjuk a Komplex típusú adatunknak lefoglalt memóriaterületet. Ezt érdemes egyből megtenni, amikor már nincsen szükség arra a változóra. A k mutató ezután továbbra is oda mutat, ahol az a komplex szám volt, de ezután már nem szabad hivatkozni a területre, hiszen visszaadtuk a malloc() nak (newnak), hogy használja újra, ha majd másra kell. Ezt a szabályt mindig, minden körülmények között be kell tartani; az nem indok, hogy úgysem foglaltunk még új memóriát, vagy hogy csak már ott van még lécci az a szám, ahol eredetileg volt. A programunk lehet többszálú, és akkor egy másik szálban bármikor lefoglalódhat az a terület más célra. Sőt akár az operációs rendszerhez is visszakerülhetett, és egy másik program használja. A többi terület felszabadítása ugyanígy történik. Érdemes megfigyelni, hogy C++ ban, amikor csak egyetlen Komplexnek foglaltunk helyet, akkor a new operátort használtuk, a felszabadításhoz pedig a delete et. A tömb foglalásakor a new[] operátor kellett, és a felszabadításhoz pedig a delete[]. A kettőt nem szabad keverni; ami new, az később delete; ami new[], az pedig később delete[]. Nem ugyanazt jelentik a new char és a new char[1] kifejezések! 3 Erre igazából C ben és C++ ban is nekünk kell figyelni. A pointeren nem látszik, hogy 3 Ennek nem csak az az értelme, hogy egy ptr=new Objektum[100] utáni delete ptr-nél csak az első objektum konstruktora hívódik meg. A new és a new[] operátorokat külön kell átdefiniálni; az egyik lehet, hogy teljesen más helyről ad memóriát, és más nyilvántartást vezet a lefoglalt területekről, mint a másik.

az egyetlen egy adatra mutat, vagy egy tömbre. Vagyis egy önálló Komplex szám memóriacíme, és egy Komplex tömb memóriacíme ugyanaz a típus: Komplex *. Ugyanígy, egy pointeren nem látszik az, hogy dinamikusan foglaltunk memóriát, és arra mutat a pointer; vagy a pointert beállítottuk egy, a globális memóriaterületen, esetleg a veremben elhelyezkedő változóra (a következő kód 6. 8. sora). Csak azt a memóriát kell kézzel felszabadítanunk, amit mi magunk foglaltunk le; a többiről a fordító gondoskodik. A veremből úgyis eltűnik, amikor vége a függvény végrehajtásának; a globális memóriaterületről pedig, amikor a programénak. 1. char global[100]; 2. 3. int main() 4. 5. char tomb[200]; 6. char *veremben; 7. char *heapen; 8. char *globalisban; 9. 10. heapen=malloc(100); 11. globalisban=global; 12. veremben=tomb; 13. 14. 15. 16. free(heapen); 17. Tömbök átadása függvényeknek, sizeof A tömböket függvényeknek kezdőcímükkel lehet átadni. A kezdőcím a tömb méretét nem tartalmazza; a függvénynek ezért semmi módja nincsen azt megtudni, hacsak explicit módon meg nem mondjuk neki. Az átadás lehetséges formái: int osszeg(int *tomb, int meret); int osszeg(int tomb[], int meret); Mind a kettő egyformán jó. Az előbbi jobban kifejezi, hogy egy kezdőcímről van szó. A függvény belsejében a tomb nevű pointert, amely csak egy pointer, akár meg is változtathatjuk (pl. tomb++); ezt gyakran csinálják sztringeket kezelő függvényeknél. A második forma kihangsúlyozza, hogy tömbről van szó, de nem teszi lehetővé a pointer megváltoztatását; illetve kicsit arra utal, mintha az egész tömb lemásolódna, ami nem igaz. Mivel a függvény az egész eredeti tömbből csak egy kezdőcímet lát, semmiképp nem használható benne a sizeof operátor. Ha belül azt írjuk, hogy sizeof(tomb), akkor egy pointer méretét kapjuk meg, nem pedig a tömb méretét. Ez sztringeknél okoz sok keveredést. char s1[20]="hello"; char s2[50]="hello"; char *s3="hello"; char s4[]="hello";

A sizeof(s1) kifejezés értéke 20, mert 20 darab karakter a tömb mérete (sizeof(char) definíció szerint 1). A sizeof(s2) kifejezésé pedig 50, ugyanezen okból. Hiába tartalmazza ugyanazt a sztringet! A sizeof(s3) értéke gépfüggő, lehet például 4, ha éppen az adott gépen annyi bájt a pointer mérete. A sizeof(s4) az 6, mert megint csak a tömb méretét kérdezzük; 5 karakter a hellónak és 1 a lezáró nullának. A sizeof a típus méretét adja meg, nem figyel a tartalomra! Ugyanakkor strlen(s1)=strlen(s2)=strlen(s3)=strlen(s4)=5, mert 5 betűből áll a szó; valószínű erre vagyunk kíváncsiak. A memóriakezelési hibák látható jelei A memóriakezelési hibák kellemetlen tulajdonsága, hogy sokszor észrevétlenül maradnak. Egy felszabadítatlan memóriaterületnek a heapen például semmi látható hatása nincs, csak annyi, ha a windowsos feladatkezelőben, linuxos topban vagy hasonló helyen nézzük a program memóriaigényét, akkor azt látjuk, hogy egyre csak nő. Eszi el a többi programtól a memóriát, észre meg csak akkor vesszük, ha már lassulni kezd miatta a gép. Minél nagyobb a programunk, ez annál könnyebben lehet gond. Kellően összetett, memóriaszivárgással teli programhoz ha új részt írunk, már kideríteni sem tudjuk, hogy vajon az új kódrészletekkel rontottunk e a helyzeten. Másik gyakori jelenség a memóriakezelési hibák esetén a változók értékeinek misztikus megváltozása. Ilyesmi akkor szokott előfordulni, ha egy lefoglalt tömböt nagyobbnak gondolunk, mint amekkora valójában. char tomb[10]="ez"; char tomb2[20]="az"; strcpy(tomb, "memoriakezelesi hiba"); A fenti kódrészletben például a tíz karakterből álló tömbbe, amely egyébként kilenc betűt és a lezáró nullát tartalmazhatja maximum, egy sokkal nagyobb sztringet másolnánk. Ettől lehet, hogy tomb2 meg fog változni. De az is lehet, hogy nem. Az is lehet, hogy lefagy a programunk, de akár előfordulhat az is, hogy észrevétlenül fut tovább. Minden attól függ, hogy a fordító hogyan helyezte el a tömbjeinket a memóriában. Ez különösen akkor érdekes, ha a két tömb egy függvény lokális változója, ugyanis akkor a veremben vannak, és a verem nem csak változókat, hanem például azt a memóriacímet is tartalmazza, ahol a program végrehajtását folytatni kell a függvényből kilépés után. Ha azt véletlenül felülírjuk, az elszállás garantált. A fordítón múlik, hogy hogyan helyezi el a tömböket. char tomb=malloc(10); char tomb2=malloc(20); strcpy(tomb, "memoriakezelesi hiba"); Ugyanez a helyzet a dinamikusan lefoglalt esetben is. Ha tomb után tomb2 van a memóriában, felülírjuk. Ha egy éppen lyukas rész van ott (lásd a Komplex felszabadítása), akkor észre sem vesszük. A program különböző futtatásai során ráadásul máshol találhat nekünk szabad memóriát a malloc(): egyszer működik, másszor pedig lefagy. Egyik gépen működik, a másikon meg nem, mert esetleg nem ugyanaz a memóriakezelési stratégia. A konklúzió az, hogy akkor van szerencsénk, ha legalább lefagy a program. Akkor legalább kiderül, hogy valami baja van. Feladatok Van hiba az alábbi kódrészletekben? Ha igen, hol?

1. feladat int *fv() int t[]=1, 2, 3, 4, 5; return t; 2. feladat char *fv() char *szo="hello"; return szo; 3. feladat const char *szam2string(int i) static char str[30]; sprintf(str, "%d", i); return str; 4. feladat const char *ki_vagy() return "Pistike"; 5. feladat int *fv() int tarolo[30]=1, 2, 3, 9; static int *statptr; statptr=tarolo; return statptr; 6. feladat class String char *szo; String(const char *init) szo=new char[strlen(init)]; strcpy(szo, init); ~String() delete[] szo; ; 7. feladat class String char *szo;

; String() szo=""; String(const char *init) szo=new char[strlen(init)+1]; strcpy(szo, init); ~String() delete[] szo; 8. feladat class Tarolo Adat **t; int db; Tarolo(int i) db=0; t=new Adat* [i]; ~Tarolo() delete[] t; berak(adat *a) t[db++]=a; ; Adat m; Tarolo tar(10); tar.berak(new Adat); tar.berak(&m); 9. feladat char *ptr=new char(80); strcpy(ptr, "Nederlandse Spoorwegen"); delete[] ptr; 10. feladat class Tarolo Adat **t; int db; Tarolo(int i) db=0; t=new Adat* [i]; ~Tarolo() for (int i=0; i<db; ++i) delete t[i]; berak(adat *a) t[db++]=a; ; Adat m; Tarolo tar(10); tar.berak(new Adat); tar.berak(&m); 11. feladat /* mit ír ki? */ const char *egyik="hello"; const char *masik="hello"; if (egyik==masik) printf("egyforma"); else printf("nem egyforma"); 12. feladat class String

char *ptr; String(char *init) ptr=new char[strlen(init)+1]; strcpy(ptr, init); delete[] init; ; 13. feladat class String char *ptr; String() /* Üres string: egy szem lezáró nulla. Inicializáljuk is. */ ptr=new char('\0'); String(const char *init) ptr=new char[strlen(init)+1]; strcpy(ptr, init); ~String() delete[] ptr; ; 14. feladat /* kétdimenziós, négyzetes mátrix determinánsát számolja */ double determinans(double **matrix, int meret); double matr[3][3]; printf("%g", determinans(matr)); 15. feladat char s[30]="hello"; printf("%s", &s); 16. feladat int *t=new int[300]; free(t); 17. feladat void nagybetus(char *t) for (int i=0; t[i]!=0; t++) t[i]=toupper(t[i]); const char str1[]="hello"; int main() char *str2="hello"; const char str3[]="hello"; nagybetus(str1); nagybetus(str2); nagybetus(str3);

18. feladat class Vektor int meret; double *adat; Vektor (int meret=3) : meret(meret), adat(new double[meret]) for (int i=0; i<meret; ++i) adat[i]=0; friend Vektor operator*(double d, const Vektor& v); ; Vektor operator*(double d, const Vektor& v) Vektor temp; temp.meret=v.meret; temp.adat=new double[temp.meret]; for (int i=0; i<temp.meret; ++i) temp.adat[i]=d*v.adat[i]; return temp; Megoldások 1. feladat A tömb a függvény lokális változója, a veremben jött létre. Nem adhatunk vissza rá pointert, mert ahogy a függvényből kijöttünk, már nem létezik. 2. feladat Nincs benne hiba. A függvényben csak a pointer a lokális változó; a sztring a globális memóriaterületen van, vagyis a függvényen kívül is létezik. 3. feladat Ebben sincs hiba. A tömb, bár lokálisnak van kikiáltva, statikus. Vagyis igazából globális változóként viselkedik, amely megmarad a függvényből kilépés után is. (Ez az egyetlen olyan hely, ahol a static kulcsszó tényleg azt csinálja, amit az angol szó jelent.) A függvény a számból sztring létrehozásának egy igazából nem túl szerencsés megvalósítása. A karakter tömbből ugyanis csak egyetlen példány van; a hívások között megtartja az értékét, de több hívás során már nem. Például az alábbi sor nem írja ki a 4 et és az 5 öt. Nem szeretjük a globális változókat, ugyebár. printf("%s %s", szam2string(4), szam2string(5)); 4. feladat Teljesen jó. "Pistike" típusa const char[]; a globális memóriaterületen létrejött konstans tömb. Megmarad a hívás után, sőt létezett már előtte is.

5. feladat A pointer ugyan statikus (globális memóriaterületen van), vagyis az értéke megmarad a hívások között... A tömb viszont, amire mutat, az meg fog szűnni a függvény végén, mert az meg a veremben van. Ezért ez hibás. 6. feladat Az odaírt dolgokban a hiba, hogy a sztringnek eggyel több karakter kell, mint strlen(init); mert az strlen() csak az értékes karaktereket számolja, a lezáró nullát viszont nem. Az oda nem írt dolgok pedig: kell másoló konstruktor és értékadó operátor. (Ha a destruktor, másoló konstruktor, értékadó operátor közül bármelyik kell, akkor általában mind a három kell.) 7. feladat Szinte teljesen jónak tűnik, de mégsem. Ha a paraméter nélküli konstruktort hívjuk, akkor a szo pointer egy globális memóriaterületen elhelyezett üres sztringre fog mutatni. Ha egy ilyen objektum destruktora fut, az fel akarja szabadítani ezt a memóriaterületet, de nem fog menni; a pointer ugyanis nem a new[] operátortól származik. 8. feladat Az osztály kódja jónak néz ki, ahogy használjuk, az viszont tervezési hibára utalhat. Pointereket tárol el Adat objektumokra; adunk neki olyan pointert is, amelyik globális (vagy veremben lévő) objektumra mutat, meg olyat is, amelyik a heapen van (new Adat). Viszont ha nem tartjuk nyilván, hogy melyik melyik, akkor nem fogjuk tudni utólag, melyiket kell delete elni, melyiket nem. (Egy pointeren nem látszik, hogy globális, verem, vagy heap területre mutat e!) Vagy esetleg ha ez a két berak() hívás egy függvényen belül történt, akkor abból kikerülve m objektum megszűnik, a tároló viszont még mindig tárolja a pointerét, ami előbb utóbb hibához vezet majd. 9. feladat A puskázók és egymásról másolók tipikus hibája. :) A 80 karakterből álló tömb a heapen ugyanis nem new(80), hanem new[80]. A kerek zárójel azt jelenti, hogy egyetlen egy karaktert foglalunk le, aminek 80 as értéket adunk (amely a nagy 'P' betű ASCII kódja). 10. feladat Hasonló a 8 as feladat tárolójához, de ez úgymond örökbe fogadja az objektumokat, vagyis deleteli őket, ha ő maga is megszűnik. A deletelés módja rendben van; de a tömböt, amelyik az egyes Adat okra mutató pointereket tartalmazza (amit a konstruktorban hozunk létre), azt nem szabadítja föl. Vagyis tartalmaz az osztály egy memóriaszivárgást. Az sincsen rendben, ahogyan használjuk; ennek a tárolónak a deletelés miatt csak dinamikusan lefoglalt objektumokat adhatunk, ezért berak(&m) is hibás. Kényes kérdés egyébként itt a másoló konstruktor. Ha lemásoljuk a tárolót, akkor le kell másolnunk a benne lévő objektumokat is (mert a destruktora deletel). Nem elég csak átmásolni az Adat ** pointert, hanem új pointereket tároló tömb kell; és nem elég az azon

belül tárolt pointereket sem átmásolni, hanem mindegyik objektumról másolatot kell készíteni egyesével. 11. feladat Nem lehet megmondani, mit ír ki. Nem a két sztringet hasonlítjuk össze, hanem a rájuk mutató pointereket. Két pointert deklaráltunk csak, nem pedig két tömböt. Ráállítottuk őket az egyik, illetve a másik hello sztringre, amelyeket a globális memóriaterületen helyeztünk el. Mivel a sztring literálisok ("hello") típusa const char[], az általuk tárolt karakterek konstansok. Ha pedig konstansok, akkor úgysem fognak megváltozni. Ezért a fordító, ha felfigyel rá, hogy egyforma a két sztring, megteheti azt, hogy csak egy másolatot tárol el belőle. Így kisebb lehet a program. GCC vel kipróbálva, ha egy fájlban van definiálva a két sztring, akkor egyformák a pointerek. Ha két külön.c fájlban vannak, akkor linkeléskor már nem keresi, hogy egyformák e; és különbözőek lesznek a pointerek. 12. feladat Nem biztos, hogy hibás; igazából ez nem kódolási, hanem tervezési hiba lehet. A konstruktorban delete[] eljük azt a karakter tömböt, aminek a másolatát tárolja a String objektum. Annyiban rossz gondolat ez, hogy a tömbért, amit a Stringnek csak le kell másolnia, nem a String a felelős, hanem a hívó. Ha valamiért mégis így döntünk, az nem csak logikátlan felépítését jelenti a programnak: onnantól kezdve ezt a Stringet csak dinamikusan lefoglalt karakter tömbből lehet inicializálni. Még olyat se írhatunk, hogy String s("hello"); mert itt a paraméterben megadott sztring globális területen van. 13. feladat Nem lenne rossz, lefoglalunk egyetlen karaktert, mert úgyis csak a lezáró nulla kell, és egyből be is másoljuk azt a nullát. De amit new operátorral foglaltunk le, azt később nem lehet delete[] operátorral felszabadítani. (Ezt a kettőt a másik irányban, illetve a malloc free párossal sem szabad keverni.) 14. feladat Nem jó típust adunk át paraméternek. A matr[3][3] egy kétdimenziós tömb, amely igazából 3*3=9 darab szám sorfolytonosan elhelyezve a memóriában. A paraméterben lévő matrix pedig: double **matrix, vagyis double *matrix[], vagyis double számokra mutató pointerek tömbje. Ez nem lehet kompatibilis az elsővel (ott nincsenek pointerek). 15. feladat Nem jó, printf("%s", s) a helyes. A típus nem egyezik; s típusa 30 karakterből álló tömb, amely kezdőcímével adódik át a függvénynek, amely egy karakterre mutató pointert vár. Más kérdés, hogy ott több karakter is lesz. &s pedig nem karakterre mutató pointer, hanem 30 elemű karakter tömbre mutató pointer. És megint csak más kérdés, hogy annak az értéke történetesen ugyanaz, mint s nek. Változó hosszúságú paraméterlista esetén nincsen típusellenőrzés. Aki nem hiszi, járjon utána: strlen(&s) nem működik;

printf("%s", (&s)+1) sem írja ki, hogy ello. 16. feladat Tipikus otthon még működött hiba. Nem csak az a baj, hogy ha a lefoglalt objektumoknak lenne destruktora (az intnek mondjuk nincs), az nem fog meghívódni a free() esetén; hanem hogy a new[] delete[] lehet, hogy különböző területről ad memóriát, vagy más nyilvántartást vezet a lefoglalt területekről, mint a malloc free. Nagyon gyakran van az, hogy a new és a new[] a háttérben a malloc() hívást használja, némelyik fordítót így írták meg, némelyiket meg nem. Ezért néhol véletlenül jól fut ez a programrész, máshol meg memóriakezelési hibaüzenettel leáll. 17. feladat Egyik függvényhívás sem jó. str1 esetén konstans tömböt deklarálunk a globális memóriaterület konstans részén. A fordító a függvényhívásra jelezni fogja, hogy nem kellene. Ha egy casttal ráerőltetjük, akkor meg futás közben a processzor fogja jelezni, hogy nem lehet írni a csak olvashatóként megjelölt memóriaterületre. A második esetben (str2) egy pointert deklarálunk csak, amelyet a konstans globális területen létrehozott tömbre állítunk. A fordító a deklarációnál fogja jelezni, hogy nincs rendben a dolog; a hívásnál nem. A futás közben ugyanúgy memóriahibát fog jelezni a processzor (segmentation fault). A harmadik esetben (str3) a fordító jelez, hogy konstans tömbre hívjuk meg a függvényt. Ha egy casttal meggyőzzük akkor a program lefut, a sztring valóban nagybetűs lesz. Azért, mert az egy tömb a veremben; a verem pedig nem helyezkedhet el csak olvasható memóriaterületen. Akkor nem lehetne használni semmire. A globális memóriaterületből igazából kettő van, egy csak olvasható, és egy írható olvasható. A read only (csak olvasható) memóriaterület, mint lehetőség, csak a globális változók esetén működik; a legtöbb mai processzor szerencsére támogat ilyet. Valami múzeumi gépen, régi fordítóval, a megfelelő castokkal mind a három hívás működésre bírható. 18. feladat (Destruktor, másoló konstruktor és értékadó operátor kell, természetesen. De azon kívül.) Memóriaszivárgást csinál az operator* függvény. Ez ugyanis létrehoz egy vektort, temp néven. Ez a vektor az alapértelmezett konstruktorával jön létre, hármas mérettel. Vagyis a temp.adat lefoglalt memóriaterületre mutat; amelyre a pointert a temp.adat=new double[temp.meret]; értékadás felülír, így az elveszik. Lehet javítani pl. úgy, hogy az új memóriaterület foglalása előtt felszabadítjuk a konstruktor által lefoglaltat, de az se nem szép, se nem gyors megoldás. Jobb megoldás az, ha a konstruktornak megadjuk paraméterben a v vektor méretét (Vektor temp(v.meret)), mert akkor eleve akkora terület jön létre, amekkora a szorzatnak kell, és nem kell felszabadítani és újra foglalni sem. Harmadik megoldás, ha a másoló konstruktort használjuk, ugyanerre a célra, hiszen az is pont akkora vektort hoz létre, mint v, amely pedig pont akkora, amekkorának a szorzatnak is kell lennie.