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:

  1. scanf() formátuma nem felel meg a magadott adtatípusnak.
  2. scanf() -ben lemarad az &
  3. inicializalatlan, vagy nem érvényes pointer használata
  4. Hibás file vége kezelés. (A scanf nem 0-t ad, ha file vége van !!!)
  5. Ne használja a fflush() függvényt input adatfolyamra! Az fflush(stdin) nem szabványos, és nem várt hatásai lehetnek!
  6. Fix méretű pufferbe olvas be, annak ellenére, hogy a feladat nem rögzíti a maximális sorhosszt.
  7. Nem veszi figyelembe, hogy a scanf("%s" ....) beolvasás az első szóköznél megáll.
  8. 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.
  9. A malloc(0) a legtöbb implementációban elszáll.
  10. 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.
  11. 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;
    } 
    
  12. A hiedelmek ellenére az állapotgép sokkal egyszerűbb és átláthatóbb kódot eredményez.
  13. 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 a

    Core 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).
    **A gdb ú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!

Utolsó frissítés: 2016-02-10 14.20