Programozási Nyelvek (C++) Gyakorlat Gyak 01. Török Márk tmark@caesar.elte.hu D-2.620 1
Tartalom Hello Világ! Fordítás, futtatás, debuggolás Interpretált vs bájtkód Névtér printf vs cout Fejállományok Streamek 2
Tartalom Hello Világ C vs C++ Különbségek, okai Egyszerűsítés (?) Mi az a belépési pont? Mikor van vége? stdio, stdlib 3
Hello Világ (C) Példa 1 : #include <stdio.h> /* * Több soros komment */ int main() { printf("hello World\n"); return 0; // egy soros komment } 4
Hello Világ (C) Példa 2 : #include <stdio.h> #include <stdlib.h> /* * Több soros komment */ int main() { printf("hello World\n"); system("pause"); return 0; // egy soros komment } 5
Hello Világ (C) Fordítás $ gcc main.c $ ls main.c, a.out (ez futtatható)./a.out Hello Világ! $ gcc main.c -o main $ ls main.c, main (ez futtatható)./main Hello Világ! 6
Hello Világ (C++) #include <iostream> /* * Komment maradt a régi */ int main() { std::cout <<"Hello World\n" << std::endl; } 7
Hello Világ (C++) Fordítás $ g++ main.cpp $ ls main.cpp, a.out (ez futtatható)./a.out Hello Világ! $ g++ main.cpp -o main $ ls main.cpp, main (ez futtatható)./main Hello Világ! 8
Hello Világ C vs C++ C : procedurális, struktúrális, C++: objektumorientált (később) stdio.h iostream printf std::cout A változások okai az objektumorientált programozási paradigmák követése, új könyvtárak, állományok implementálása Belépési pont: int main() vagy int main(int argc, char* argv[]) vagy int main(int argc, char** argv) 9
Hello Világ C vs C++ ISO C++ standard 3.6.1 void main(/* */) { } main(/* */) { } Kilépési pont, visszatérési érték típusa mindig int. Visszatérés legyen 0! Minden más érték a hibáké! C++ esetében: Nem kötelező megadni visszatérési értéket main() esetében ez mindig 0 lesz! Más esetén egy memóriaszemét. 10
Hello Világ stdio.h C Standard Input and Output Library C-ben stdio.h C++ cstdio Az input-output műveletekért felelős macrok, típusok, függvények, konstansok Billentyűzet, nyomtató, egyéb perifériaeszközök stream: lehetőséget biztosít ezek írására, olvasására. stdin, stdout, stderr (később) 11
Hello Világ stdlib.h C Standard General Utilities Library Memóriahasználathoz szükséges metódusok calloc, malloc, realloc, free Véletlenszám generálás Környezettel kommunikáló metódusok: exit, system, Konverzió Keresés, rendezés... 12
Fordítás, futtatás, debuggolás Ahogy már láttuk: $ g++ main.cpp -o main $./main Parancssori paraméterrekel $./main param1 param2 argv[0] maga a futtatható állomány; argv[1],... a tényleges paraméterek Hibákat irassuk ki: $ g++ -Wall main.cpp -o main Error, warning Hiba kiírása: fájlnév:sor száma:hibaüzenet 13
Fordítás, futtatás, debuggolás printf format specifiers %i or %d int %c char %f float %lf double %s string int a = 9; int b = 10; int c = 100; printf("%d %d %d\n", a, b, c); // 9 10 100 printf("%3d %3d %2d\n", a, b, c); // 9 10 100 printf("%3d %3d %2d\n", a, b, c); // 009 010 100 14
Fordítás, futtatás, debuggolás Nézzünk egy példát: #include <iostream> bool f() { } int main() { std::cout << f(); } $ g++ main.cpp -Wall -o main main.cpp: In function bool f() : main.cpp:4: warning: control reaches end of non-void function 15
Fordítás, futtatás, debuggolás Nézzünk egy példát: #include <cstdio> int main() { printf("%f", 99); } $ g++ main.cpp -Wall -o main main.cpp: In function int main() : main.cpp:8: warning: format %f expects type double, but argument 2 has type int 16
Fordítás, futtatás, debuggolás Több állomány fordítása: void sayhello (const char* name); // hello.h #include <stdio.h> // hello.c #include "hello.h void sayhello(const char* name) { printf ("hello, %s", name); } #include "hello.h // main.c int main() { sayhello ("world"); return 0; } 17
Fordítás, futtatás, debuggolás $ gcc -Wall main.c hello.c -o hmain Header: Két félét különböztetünk meg. #include <fájl.h>: system header fájlok között nézi meg. (linuxon: /usr/include/stdio.h) #include fájl.h : a lokális mappában keres, majd a system headerek között. 18
Fordítás, futtatás, debuggolás Fordítás lépései: Forrásból object: először nem is futtatható fájl keletkezik, hanem egy object. Ennek kiterjesztése.o. A második lépés a linkelés: a linker az objectfájlok összefésülését teszi lehetővé. Ebből lesz a futtatható állomány. $ gcc -Wall -c main.c Eredmény egy main.o, mely a főprogramunk gépi kódját tartalmazza. $ gcc main.o -o main 19
Fordítás, futtatás, debuggolás Külső könyvtárakból: Archive állományok, kiterjesztése:.a // statikus könyvtárak Matematikai függvények Math.h-ban, ez viszont a libm.a könyvtárban. $ gcc -Wall calc.c /usr/lib/libm.a -o calc Kapcsoló: $ gcc -Wall calc.c -lm -o calc Ez annyit jelent, hogy lm = libm.a 20
Fordítás, futtatás, debuggolás Library-k: Static library és shared library Static library kiterjesztése:.a A linkelést követően az használt függvény gépi kódja a library-ból bemásolódik a futtatható állomány-ba. Shared library kiterjesztése:.so Dinamikus kötés (dynamic linking): a shared library táblázatot tartalmaz hivatkozással az egyes függvényekre. Fordításkor a linker egy ilyen hivatkozást rak be a futtatható állományba, a teljes gépi kód helyett. A futtatáskor a gépi kód bemásolódik a memóriába a megadott hivatkozás alapján. 21
Fordítás, futtatás, debuggolás -Wall kapcsoló: -Wreturn-type: figyelmeztet, hogy az adott függvény definíció szerint kér visszatérési értéket (azaz nem void), de ezt az implementációjában nem tettük meg. -Wformat: hibás formatstring a printf, scanf függvényekben. Eltér a format a paraméter típusától. -Wunused: figyelmeztet, ha használatlan változók vannak a kódban. -Wimplicite: ha előzőleg nem adtuk meg a függvény specifikációját. 22
Fordítás, futtatás, debuggolás Preprocessor: A fordító által meghívott, a tényleges fordítás előtt lefutó program. Mit csinál? Kezeli az alábbi direktívákta: #include : forrásmegjelölés #define: macro definiálása #if: feltételes forrásbetöltés, macrodefiniálás 23
Fordítás, futtatás, debuggolás Macro-k: C-ben fontosabb, C++-ban kevésbé fontos szerepet töltenek be. (Grrrrhhh!) Ha tehetjük, akkor kerüljük őket. (Nagyon erőssen ajánlott!) Mivel a fordítóprogram futása előtt a macro-k meghívásra kerülnek, és belenyúlnak a kódba, nem ajánlatos használni őket. Csökken a hatékonysága azon eszközöknek, melyekkel a programunk hatékonyságát, biztonságát tudjuk mérni. Pl.: hibakeresők, kereszthivatkozás-vizsgálók. 24
Fordítás, futtatás, debuggolás Macro-k: #define CSERELD_EZT erre csere = CSERELD_EZT A macro lefutását követően az eredmény ez lesz: csere = erre #define SQUARE(a) a*a Ebből: int b = 0; int i = SQUARE(b + 2); Igen ám! De ebből: b + 2*b + 2 => 3b + 2 lesz! 25
Fordítás, futtatás, debuggolás Macro-k: Feltétel: #ifdef AAA printf( ez az ág lefutott! ); #endif Fordítás: $ gcc -Wall -DAAA main.c A -D kapcsoló prefixe az adott AAA nevű macro-nak. Így tudunk a macro-nak értéket is adni. Nyilván ha ezt a kapcsolót kihagyjuk, az adott ág lefutása is elmarad.
Fordítás, futtatás, debuggolás Debuggoláshoz: Fordítsunk a -g kapcsolóval. Hogy miért? Amikor a programunk abnormális futást produkál (elszáll menetközben), az operációs rendszer elmenti a program memóriabeli állapotát egy core nevű fájlba. Nézzük meg, hogy mi van a core fájlba.
Fordítás, futtatás, debuggolás Töltsük be a core fájlt a GNU Debuggerbe az alábbi módon: $ gdb futtatható-állomány core-állomány Csak együtt tudjuk őket használni, külön nem tudjuk betölteni a core-állományt. $ gdb a.out core Core was generated by./a.out. Program terminated with signal 11, Segmentation fault. Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 #0 0x080483ed in a (p=0x0) at null.c:13 13 int y = *p; (gdb)
Fordítás, futtatás, debuggolás Debuggolás: Stack backtrace kilistázása: (gdb) backtrace Kilistázza a hívási listát.
Interpretált vs bájtkód C++-forráskód lefordításával hagyományos natív kódot kapunk. A natív kód a processzornak megfelelő módon lefordított gépi kód. Ha natív C++-alkalmazásunkat több különböző környezetben (32bites, 64bites platformon) is szeretnénk futtatni, akkor külön le kell fordítanunk.
Interpretált vs bájtkód Hogy megy ez máshol? C# kód fordításával kezelt kódot kapunk. Ez egy közbenső nyelv (intermediate language, IL). A magas szintű (C#) és a legalacsonyabb szintű nyelv (assem., gépi kód) között helyezkedik el. A közös nyelvű futási idejű környezet (Common Language Runtime, CLR) futási időben, menet közben fordítja le a kódot a Just-in-time (JIT) fordító alkalmazásával.
Interpretált vs bájtkód Mit csinál a JIT fordító? Nem túl hatékony a kódot futásidőben fordítani? Ez nem interpreting! A JIT-fordító nem fordít le egy adott metódust vagy függvényt annak minden egyes meghívásakor, ezt csak az első esetben teszi meg, és ekkor a platformnak megfelelő gépi kódot állít elő. Használatával csökken az alkalmazás munkahalmaza, a közbenső kód munkaigénye kisebb lesz.
Interpretált vs bájtkód Hogy megy ez Java-ban? A forrásfájlokban (.java) definiált minden egyes típus fordítása eredményeként külön létrejövő típusnév.class fájl tartalmazza a Java virtuális gép által végrehajtható bináris bájtkódot. A hordozható bájtkódot interpretálás helyett, közvetlenül futtatás előtt platformfüggő gépi kódra fordítja át, mely kód sokkal gyorsabban fut majd, mint a bájtkód interpretálása. Előnye, a biztonság és a hordozhatóság, hátránya, hogy néhol lassú.
Interpretált vs bájtkód Jittelés Java-ban: A gyorsabb futás érdekében dinamikus fordítási technika használata: JIT (Just-in-time compiler). Minden osztály bájtkódjának betöltése után az egész osztály bájtkódját lefordítja gépi kódra. (A további használatban a kód már nem interpretált.) Előny: többszörösen is gyorsabb lehet a Java kód végrehajtása, viszont egy osztály bájtkódjának (vagy egy metódus végrehajtása) betöltési ideje növekszik.
Fejállományok Azaz a header-ök! Lehetővé teszik a kód elkülönítését. Tartalma: forward declaration (forward reference) Osztályok: class Clazz; Alprogramok: void get(clazz &clazz); Váltózókat: hivatkozhatsz egy változóra, mielőtt deklarálnád. class Clazz { public: int get() { return value; } private: int value; } Egyéb azonosítók. 35
Fejállományok Programok kisebb részekre bontása Osztályokra Alprogramokra Nézzük meg, hogy épül fel: get.h #ifndef GET_H_GUARD #define GET_H_GUARD void get(int a); #endif Használata: #include get.h int f() { return get(10); }
Névtér Mindig valamilyen logikai csoportosítást fejeznek ki. (Egyes deklarációk valamilyen jellezmő alapján összetartoznak.) Külön hatókört alkotnak Másik névtérből származó osztályt a névtér nevének minősítésével tudjuk elérni. Ha egy nevet sokat használunk saját névterén kívül, egyszerűsíthetünk. Használjuk a using direktívát. using namespace std;
Stream Kimenet: #include <iostream> using namespace std; Az iostream minden beépített típusra meghatároz kimenetet. Alapértelmezésben a cout-ra kerülő kimeneti értékek karaktersorozatra alakítódnak át. cout << hello vilag! << 10 << endl;
Stream Bemenet: istream: beépített típusok karaktersorozatként történő ábrázolásával dolgozik. A >> jobb oldalán álló típus határozza meg, hogy bemenet fogadható el, és mi a beolvasó művelet célpontja. int i; cin >> i;