3. diel - Céčko a Linux - Debugging
V minulej lekcii, Céčko a Linux - Makefile , sme sa venovali Makefile. Už vieme prekladať svoje programy v termináli a vieme si to aj uľahčiť. Dnes sa pozrieme na ďalšie, veľmi dôležitá téma. Určite sa vám už stalo, že ste mali v programe niekde chybu a prechádzali ste ho znova a znova ... Ale stále ju nemohli nájsť. Je to tak častý problém, že bola vytvorená kopa nástrojov, ktoré nám ju majú uľahčiť. Hovorí sa im ladiace programy a najdôležitejšie z nich je tzv. Debugger (doslovný preklad je "odvšivovací").
Indent
Začneme zľahka a pozrieme sa najskôr na tento jednoduchý nástroj pre
formátovanie kódu. Nainštalovať ho môžete príkazom
sudo apt-get install indent
a detailný popis jeho používanie
nájdete v manuálových stránkach (man indent
). Verím (dúfam),
že väčšina z vás píše pekne odsadený kód. Načo teda taký program?
Čas od času sa môžete dostať ku kódu, ktorý buď z nejakého dôvodu
stratil pôvodné formátovanie (prišiel vám cez FB) alebo autor používa
výrazne iný spôsob odsadzovanie a vás to jednoducho "štve". V takom
prípade nie je nič jednoduchšie, než si nechať preformátovať kód k
obrazu svojmu.
Malá ukážka - máme potrebné takýto nepekný kód (predstavte si, že má 10 000 riadkov). V tomto by sa vám nechcela hľadať chyba, že?
#include <stdio.h> #include <stdlib.h> int main (int argc,char argv[]){int a=b=c=1; printf("Výpočet: %d\n",(a+a+b)*(++c+c+++c);return 4;}
Spustíme teda indent soubor.c
. Toto nastavenie nám kód
sformátuje do klasického UNIXového štýlu. Ak nemáte radi odsadenie
tabulátory, použite prepínač -nut
(indent -nut soubor.c
). Ten povie indent, že nechceme tabulátory,
ale medzery. Výhoda tabulátorov je možnosť nastaviť si v editore ich
šírku. Niekto má radšej odsadenie väčšie, niekto menšie (ja preferujem
šírku dve medzery) .. Táto vlastnosť môže byť ale teoreticky aj nevýhoda
- napríklad v prehliadačoch má tabulátor štandardne šírku 8 medzier,
takže kód s tabulátormi môže mať naozaj obrovské odsadenie.
Po použití indent bude náš kód vyzerať nejako takto:
#include <stdio.h> #include <stdlib.h> int main (int argc, char argv[]) { int a = b = c = 1; printf ("Výpočet: %d\n", (a + a + b) * (++c + c++ + c); return 4; }
Nie je to samozrejme dokonalé (nepáči sa mi napríklad väčšie odsadenie returne), ale na rýchle sprehľadnenie nečitateľného kódu to stačí. Indent sa dá samozrejme (ako všetky Linuxové nástroje) dopodrobna nastaviť, takže s trochou trpezlivosti si môžete vytvoriť vlastnú konfiguráciu, ktorá bude formátovať kód presne podľa vašich predstáv.
Ešte poznámka - môžete si vytvoriť vlastný "profil" indent, ktorý bude
automaticky použitý pri každom spustení. Stačí len vo svojom domovskom
adresári vytvoriť súbor indent.pro
a napísať do neho použité
prepínača. Ich význam nájdete v manuálových stránkach.
Splint
Ďalší nástroj, na ktorý sa dnes pozrieme, slúži na analýzu kódu - vypíše nám možné problémy. Máme síce k dispozícii chybové hlášky prekladača, ale ako skoro uvidíme, tie nie vždy stačí. Pozrieme sa na nasledujúci kód - takto nejako by mohol vyzerať kód začiatočníka, čo si niekde niečo prečítal a teraz sa sám snaží vyskúšať si prácu so znakmi ...
#include <stdio.h> #include <stdlib.h> int main () { char c; while (c != 'x'); { c = getchar(); if (c = 'x') return 0; switch (c) { case '\t': printf("Zadal jsi tabulátor.\n"); default: printf("%c", c); } } return 0; }
Skúsime ho preložiť ..
$ gcc prog.c -o prog
Keď preložíme tento kód, prekladač nám nevráti žiadnu chybu. Napriek tomu ale kód nebude vôbec fungovať. Skúsenejší programátor určite vie prečo, ale začiatočník bude stratený. Prekladač nehlási chybu, ale program nefunguje. Čo s tým? V prvom diele sme hovorili o parametroch prekladača a ako by sme mali správne prekladať. Skúsime to teda znova.
$ gcc -std=c99 -Wall -Wextra -pedantic prog.c -o prog
Tentoraz už náš prekladač upozornil na dva problémy - v cykle
while
používame premennú c
bez inicializácie a v
podmienke máme namiesto porovnávanie priradenie.
warning: ‘c’ is used uninitialized in this function while (c != 'x'); warning: suggest parentheses around assignment used as truth value if (c = 'x')
Varovanie samotné ale nie je príliš detailné a ani nám nehovorí, čo by
sme s tým mali robiť. Skúsime splint prog.c
(výpis je dlhý,
takže si to každý skúste sám, aby ste presne videli, čo splint vypíše).
Splint vykoná analýzu kódu a upozorní nás na nasledujúce problémy:
- premenná c použitá pred spustením
- hrozí nekonečná slučka
- priradenie int do char: c = getchar () - hrozí strata informácie
- priradenie v podmienke
- "Fall trough case" - "prepadnutie" casom (chýba break, ak príde \ t, vykoná sa i default)
Ku každému problému je vypísaný detailný popis, prečo je to problém a ako by sa dal vyriešiť. Okrem toho sa aj dozvieme, ako toto varovanie vypnúť. Povedal by som, že splint sa môže občas celkom hodiť - najmä ak máme dlhý kód. Opravený kód si môžete stiahnuť.
GDB
Prekladač ani splint nám nehlási žiadne chyby, ale program rovnako nefunguje tak, ako sme chceli. Čo s tým? Zavoláme si na pomoc debugger. Čo to ale vlastne je?
Debugger je nástroj, ktorý nám v zásade umožňuje krokovať (vykonávať náš kód po jednotlivých príkazoch) au toho sledovať hodnoty premenných. Existuje celý rad debugger a vie celý rad vecí. My sa dnes pozrieme na klasický gdb, ktorý je taký nepísaný štandard pre debuggovania programov v C / C ++, ale zvláda aj mnoho ďalších. Je tiež často základ rôznych grafických prostredí (dnes sa pozrieme na odd, nabudúce na Code :: Blocks, ďalej napríklad Nemiver).
Väčšina debugger umožňuje preskakovať bloky kódu, čo nás nezaujímajú (vieme, že tam nemôže byť chyba), pomocou tzv. Breakpoint. Breakpoint je miesto, kde sa zastaví vykonávanie a odtiaľ typicky ďalej ideme po riadkoch a sledujeme, ako program funguje. Existuje veľké množstvo možností a módov. My sa dnes pozrieme na debuggovania len rámcovo, aby sme vedeli, ako debugger spustiť a použiť. Detailnejšie postupy a triky si snáď ukážeme niekedy v budúcnosti.
Prvá vec, ktorú musíme urobiť, keď chceme použiť debugger, je
preložiť náš kód s parametrom -g
. Ten, ako už vieme, pridá
debuggovací informácie, bez ktorých je debugger takmer nepoužiteľný. Pre
ukážku použijeme nasledujúci kód.
#include <stdio.h> #include <stdlib.h> int main () { int sum = 0; for(int i = 0; i -= 1000; i++) sum += i; printf("Součet: %d\n", sum); return 0; }
Je mi jasné, že prevažná väčšina z vás na prvý pohľad uvidí, kde je problém. Ale predpokladajme, že nie. Sme napríklad unavení alebo nastal nejaký skrat a my sme si istí, že - = je ten správny operátor. Skúsime program preložiť - žiadne chyby. Natešení teda program spustíme a čo sa nestane?
Chvíľku na výsledok nechápavo pozeráme ... Suma čísel od 1 do 1000
predsa nie je -34394132! Pozrieme na kód, ale chybu nevidíme. Celé to
prečítať a hľadať chybu sa nám nechce, takže spustíme debugger. Stačí
napísať v termináli gdb soubor
. GDB vypíše nejaké informácie
o sebe a čaká na ďalšie príkazy.
GDB môžeme ukončiť zadaním klasického "q" alebo "quit". Zadaním "help" sa nám zobrazia témy pomoci. Keď napíšeme help tému (napr. Help breakpoints), vypíšu sa nám konkrétne príkazy a ich význam. Program spustíme jednoducho napísaním "run". Keby sme to ale teraz urobili, tak len prebehne, ukáže nám výstup a návratovú hodnotu, ale nič ďalšie sa nedozvieme. Nastavíme teda breakpoint a spustíme program.
break main run
Ako vidíte, breakpoint môžeme ľahko nastaviť na začiatok funkcie.
Program pobeží, než sa dostane k Breakpoint (v našom prípade ako je
zavolaná funkcie main) a tam sa zastaví a vypíše riadok, ktorý bude
spracovávať. Teraz môžeme vypísať obsah premennej príkazom display.
Skúsime to display sum
. Vidíme, že premenná obsahuje nejakú
náhodnú hodnotu - ešte sa nevykonalo priradenie nuly. Krok prevedieme
príkazom n
(skratka pre next). Vidíme, že sa vypísal ďalší
riadok (číslo na začiatku je číslo riadka) a pod ním hodnota premennej
sum.
Vidíme, že príkaz display sum
v skutočnosti zapína
zobrazovanie premenné, nemusíme ho teda písať zakaždým znova. Vykonáme
ďalší riadok - teraz by mala byť inicializovaná premenná i. Pre istotu si
ju skontrolujeme (display i
). Hneď vidíme problém - aj by malo
byť nula, ale je -1000. Pre zaujímavosť môžeme pokračovať ďalej.
Vidíme, že suma sa počíta správne a že ki je síce s každou iterácií pripočítaná jednička, ale zároveň je odpočítaný tisíc. Vypneme debugger, chvíľu budeme nechápavo krútiť hlavou nad tým, ako sme mohli urobiť takú chybu a nakoniec ju opravíme. Hotovo, problém vyriešený.
GDB toho vie samozrejme oveľa oveľa viac, ale pre ukážku to stačí. Ešte uvediem niekoľko užitočných príkazov a pozrieme sa na DDD.
list název_fce
- vypíše zdrojový kód funkcielist 10,50
- vypíše riadky 10 až 50print proměnná
- ak chceme len raz vypísať premennú a nechceme ju hneď sledovaťinfo display
- zobrazí, ktoré premenné sledujemeundisplay číslo
- zruší sledovanie, číslo získame pomocou info displaycontinue
- program pokračuje do ďalšieho Breakpoint (ak nie je, tak do konca)
DDD
Ďalej sa krátko pozrieme na DDD (Data Display Debugger), čo je vlastne len grafická nadstavba pre debugger. Okrem GDB vie DDD pracovať s množstvom ďalších a to je tiež jedna z jeho hlavných výhod. Je ale dnes už trochu zastaraný (napríklad chýbajúca podpora UTF-8), ale stále hojne používaný a dobrá ukážka. V niektorom budúcom článku sa pozrieme na Nemiver, čo je pekná, moderná nadstavba pre GDB, určená špecificky pre GNOME. Používatelia KDE, sa môžu pozrieť napríklad na KDbg. Ďalšou alternatívou môže byť napríklad Insight.
V DDD môžeme používať rovnaké príkazy (a kľudne ich zadávať
ručne), ale môžeme tiež využiť pripravené GUI. Výhoda môže byť, že
neustále vidíme svoj kód a nemusíme poznať všetky príkazy naspamäť.
Takto nejako vyzerá DDD po spustení (ddd program
).
Tak ... Pozrieme sa na nejaké základné ovládanie. Breakpoint môžeme vytvoriť kliknutím (a držaním) pravého tlačidla na riadok.
Jednoduché, že? Všimnime si, že na riadku sa nám objavila krásna "stopka". Keď na nej klikneme (zas pravým tlačidlom), môžeme ju odstrániť, vypnúť alebo prípadne otvoriť vlastnosti (tam môžeme nastaviť napríklad podmienku - o tých ale v tomto diele hovoriť nebudeme).
Program spustíme tlačidlom run. Všimnime si, že v príkazovom riadku v spodnej časti obrazovky sa objavujú rovnaké výpisy, ako sme videli pri práci s GDB. Tiež sa objavila sympaticky zelená šípka, ktorá ukazuje na riadok, ktorý má byť vykonaný.
Ďalej nás bude určite zaujímať, ako sledovať premennú. Opäť je to jednoduché - stačí na ňu kliknúť pravým tlačidlom a vybrať si príslušný príkaz. Ja sa rozhodol, že ju chcem sledovať pomocou display. Hore sa objavila nová časť okna a v tej má premenná.
Teraz môžeme krokovať programom. Ja urobil niekoľko krokov, ale potom som si povedal, že sa mi naozaj nechce preklikávať sa celým cyklom (ktorý bude mať o veľa viac ako pôvodne plánovaných 1000 iterácií). Nastavil som si teda ďalšie breakpoint (na riadok za cyklom).
Teraz len klikneme na cont (continue) a program pobeží do ďalšieho Breakpoint. V sum sa nám ukázala finálna hodnota. Keď teraz vyberieme next, prebehne printf. Keď by sme vybrali znovu cont, program prebehne až do konca.
A to je pre dnešok všetko. V budúcej lekcii, Céčko a Linux - Code :: Blocks , sa pozrieme na prácu v Code :: Blocks. K pokročilejším debuggovacím technikám sa vrátime v niektorom z budúcich dielov.
Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.
Stiahnuť
Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami
Stiahnuté 36x (719 B)
Aplikácia je vrátane zdrojových kódov v jazyku C