Tipikus hibák
A hftest egyik célja, hogy rámutasson azokra a nagyon egyszerű, de gyakran elkövetett hibákra, melyet el szoktak követni a kezdő C illetve C++ programozók: Itt ezeket próbálom összefoglalni:
- scanf() formátuma nem felel meg a magadott adtatípusnak.
- scanf() -ben lemarad az &
- inicializalatlan, vagy nem érvényes pointer használata
- Hibás file vége kezelés. (A scanf nem 0-t ad, ha file vége van !!!)
- Ne használja a
fflush()
függvényt input adatfolyamra! Az fflush(stdin) nem szabványos, és nem várt hatásai lehetnek! - Fix méretű pufferbe olvas be, annak ellenére, hogy a feladat nem rögzíti a maximális sorhosszt.
- Nem veszi figyelembe, hogy a scanf("%s" ....) beolvasás az első szóköznél megáll.
- A stringkezelő függvények (strcpy, strcmp, str...) hibásan működnek, ha nincs lezárva a string (\0) , vagy null pointert kapnak paraméterként.
- A malloc(0) a legtöbb implementációban elszáll.
- Ezzel szemben C++-ban a new char[0] hiba nélkül lefut, és érvényes pointert ad vissza, ahol azonban nem lehet semmit tárolni.
- Az előzőek miatt igen veszélyes a következő kódrészlet, ami a from által mutatott stringet másolja egy új területre (pl. copy konstruktorban) ha a len 0 is lehet, annak ellenére, hogy a from pointert a kód ellenőrzi:
char *from; int len; ... char *p = new char[len]; if (from) strcpy(p, from);
Így lenne helyes:
char *from; int len; ... if (from) { char *p = new char[len]; strcpy(p, from); } else { p = from; }
- A hiedelmek ellenére az állapotgép sokkal egyszerűbb és átláthatóbb kódot eredményez.
- Hibás memóriakezelésnél (nem inicializált, vagy nem érvényes pointer) gyakran "Segmentation Fault (core dumped)" üzenetet kapunk. Ilyenkor a keletkező core fájl tartalmazza a teljes memóriaképét a processznek (futó programnak). Ebből és a futtaható kódban tárolt szimbóluminformációkból egy debugger segítségével (pl. gdb) viszonylag könnyen megállapítható a hiba helye és oka. Sajnos az ural2 szerveren nem keletkezik core fájl, mert a rendszergazda letiltotta azt*. Ennek ellenére álljon itt egy példa, hogy hogyan lehet használni ezt a lehetőséget más UNIX/LINUX környezteben**.
Tételezzük fel, hogy a hibas programot futtatva, melyet gcc -g -o hibas hibas.c paranccsal fordítottunk, core file keletkezik. Ekkor a gdb ./hibas core parancsal indítva a debuggert aCore was generated by 'hibas'.
....
#0 0x00010a14 in summa (t=0x0, n=4) at hibas.c:5
5 sum += *t++;
(gdb)üzenetet kapjuk, ami azt jelenti, hogy a programunk a hibas.c forrás 5. soránál állt meg, azaz itt csinált valami végzetest. Az is látszik, hogy a summa nevű függvényben vagyunk, amit t=0 értékkel hívtunk. A t-t viszont az 5. sorban címzésre (pointer) használjuk. Ez a baj! Már csak az a kérdés, hogy honnak hívtuk a summa függvényt nullával. A bt (backtrace) parancs hatására megnézhetjük, hogy mely függvényhívások voltak éppen aktívak. A példánkban ezt kaptuk:
(gdb) bt
#0 0x00010a14 in summa (t=0x0, n=4) at hibas.c:5
#1 0x00010a54 in main () at hibas.c:11
(gdb)Ebből azt látjuk, hogy a main() függvény a hibas.c állomány 11. sorában hívta a summa függvényt t=0, és n=4 paraméterrel. Az up parancs hatására belenézhetünk a hívó függvénybe:
(gdb) up
#1 0x00010a54 in main () at hibas.c:11
11 summa(p, 5);
(gdb)Ebből az látszik, hogy a hibas.c állomány 11. sorából hívtuk a summa függvényt p és 4 paraméterekkel. Már tudjuk, hogy a p nulla, de ezt meg is nézhetjuk a p (print) paranccsal:
(gdb) p p
$2 = (int *) 0x0
(gdb)Hát ez tényleg nulla! Most egy lis (list) paranccsal nézzük meg a forrást (a hibas.c az aktuális könyvtárban kell, hogy legyen):
(gdb) lis
6 }
7
8 int main()
9 {
10 int *p = 0;
11 summa(p, 5);
12 }
13
(gdb)Így már érthető, hogy mi történt. Ez tényleg hibás!
*A core fájl maximális mérete az ulimit vagy limits paranccsal szabályozható. Solaris rendszerben – ilyen az ural2 – egy globális rendszerparaméterrel is szabályozható. Egyes rendszerekben a core fájl neve is konfigurálható (coreadm, sysctl, /proc/sys/kernel/core_name_format).
**Agdb
úgy is használható, hogy segítségével futtatjuk a programot. Ekkor nincs core fájlra szükségünk. A fenti programot pl. a gdb ./hibas paranccsal futtahatjuk. Ekkor a gdb a futtaható programot betölti a memóriába, ami az r (run) gdb paramcssal indítható el. Ha parancsnyelvi argumentumokat is kell megadni a programunknak, akkor azt a set args gdb parnccsal tehetjük meg.
Ha valaki úgy érzi, hogy nem jut előre a megoldás során, akkor bátran kérdezzen meg szóban vagy írásban! Megpróbálom rávezetni a hibára. Mindig a gépnek van igaza. A ráolvasásos módszerek nem segítenek! Ha mégis úgy tűnik, akkor meg kell érteni, hogy valójában mi történt!