IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Céčko a Linux - Debugging

V minulé lekci, Céčko a Linux – Makefile, jsme se věnovali makefile. Již umíme překládat své programy v terminálu a umíme si to i usnadnit.

Dnes se podíváme na další, velmi důležité téma. Určitě se vám již stalo, že jste měli v programu někde chybu a procházeli jste ho znovu a znovu... Ale pořád ji nemohli najít. Je to tak častý problém, že byla vytvořena spousta nástrojů, které nám ji mají usnadnit. Říká se jim ladící programy a nejdůležitější z nich je tzv. debugger (doslovný překlad je „odvšivovač“).

indent

Začneme zlehka a podíváme se nejdříve na tento jednoduchý nástroj pro formátování kódu. Nainstalovat ho můžete příkazem sudo apt-get install indent a detailní popis jeho používání najdete v manuálových stránkách (man indent). Věřím (doufám), že většina z vás píše pěkně odsazený kód. K čemu tedy takový program? Čas od času se můžete dostat ke kódu, který buď z nějakého důvodu ztratil původní formátování (přišel vám přes FB) nebo autor používá výrazně jiný způsob odsazování a vás to jednoduše "štve". V takovém případě není nic jednoduššího, než si nechat přeformátovat kód k obrazu svému. :)

Malá ukázka - máme třeba takovýto nepěkný kód (představte si, že má 10 000 řádků). V tomhle by se vám nechtěla hledat 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 tedy indent soubor.c. Toto nastavení nám kód zformátuje do klasického Unixového stylu. Pokud nemáte rádi odsazení tabulátory, použijte přepínač -nut (indent -nut soubor.c). Ten řekne indentu, že nechceme tabulátory, ale mezery. Výhoda tabulátorů je možnost nastavit si v editoru jejich šířku. Někdo má radši odsazení větší, někdo menší (já preferuji šířku dvě mezery).. Tato vlastnost může být ale teoreticky i nevýhoda - například v prohlížečích má tabulátor standardně šířku 8 mezer, takže kód s tabulátory může mít opravdu obrovské odsazení.

Po použití indent bude náš kód vypadat nějak 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;
          }

Není to samozřejmě dokonalé (nelíbí se mi například větší odsazení returnu), ale na rychlé zpřehlednění nečitelného kódu to stačí. Indent se dá samozřejmě (jako všechny Linuxové nástroje) dopodrobna nastavit, takže s trochou trpělivosti si můžete vytvořit vlastní konfiguraci, která bude formátovat kód přesně podle vašich představ.

Ještě poznámka - můžete si vytvořit vlastní "profil" indentu, který bude automaticky použit při každém spuštění. Stačí jen ve svém domovském adresáři vytvořit soubor indent.pro a napsat do něj použité přepínače. Jejich význam najdete v manuálových stránkách.

splint

Další nástroj, na který se dnes podíváme, slouží k analýze kódu - vypíše nám možné problémy. Máme sice k dispozici chybové hlášky překladače, ale jak brzy uvidíme, ty ne vždy stačí. Podíváme se na následující kód - takhle nějak by mohl vypadat kód začátečníka, co si někde něco přečetl a teď se sám snaží vyzkoušet si práci se znaky...

#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;
}

Zkusíme ho přeložit..

$ gcc prog.c -o prog

Když přeložíme tento kód, překladač nám nevrátí žádnou chybu. Přesto ale kód nebude vůbec fungovat. Zkušenější programátor jistě ví proč, ale začátečník bude ztracený. Překladač nehlásí chybu, ale program nefunguje. Co s tím? V prvním díle jsme mluvili o parametrech překladače a jak bychom měli správně překládat. Zkusíme to tedy znovu.

$ gcc -std=c99 -Wall -Wextra -pedantic prog.c -o prog

Tentokrát již náš překladač upozornil na dva problémy - v cyklu while používáme proměnnou c bez inicializace a v podmínce máme místo porovnávání přiřazení.

warning: ‘c’ is used uninitialized in this function while (c != 'x');
warning: suggest parentheses around assignment used as truth value  if (c = 'x')

Varování samotné ale není příliš detailní a ani nám neříká, co bychom s tím měli dělat. Zkusíme splint prog.c (výpis je dlouhý, takže si to každý zkuste sám, abyste přesně viděli, co splint vypíše). Splint provede analýzu kódu a upozorní nás na následující problémy:

  • proměnná c použita před inicializací
  • hrozí nekonečná smyčka
  • přiřazení int do char: c = getchar() - hrozí ztráta informace
  • přiřazení v podmínce
  • "fall trough case" - "propadnutí" casem (chybí break, pokud přijde \t, vykoná se i default)

Ke každému problému je vypsán detailní popis, proč je to problém a jak by se dal vyřešit. Kromě toho se i dozvíme, jak toto varování vypnout. Řekl bych, že splint se může občas docela hodit - zvláště pokud máme dlouhý kód. ;) Opravený kód si můžete stáhnout.

GDB

Překladač ani splint nám nehlásí žádné chyby, ale program stejně nefunguje tak, jak jsme chtěli. Co s tím? Zavoláme si na pomoc debugger. Co to ale vlastně je?

Debugger je nástroj, který nám v zásadě umožňuje krokovat (provádět náš kód po jednotlivých příkazech) a u toho sledovat hodnoty proměnných. Existuje celá řada debuggerů a umí celou řadu věcí. My se dnes podíváme na klasický gdb, který je takový nepsaný standard pro debuggování programů v C/C++, ale zvládá i mnoho dalších. Je také často základ různých grafických prostředí (dnes se podíváme na ddd, příště na Code::Blocks, dále například Nemiver).

Většina debuggerů umožňuje přeskakovat bloky kódu, co nás nezajímají (víme, že tam nemůže být chyba), pomocí tzv. breakpointů. Breakpoint je místo, kde se zastaví vykonávání a odtud typicky dále jdeme po řádcích a sledujeme, jak program funguje. Existuje velké množství možností a módů. My se dnes podíváme na debuggování jen rámcově, abychom věděli, jak debugger spustit a použít. Detailnější postupy a triky si snad ukážeme někdy v budoucnu.

První věc, kterou musíme udělat, když chceme použít debugger, je přeložit náš kód s parametrem -g. Ten, jak už víme, přidá debuggovací informace, bez kterých je debugger téměř nepoužitelný. Pro ukázku použijeme následující 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 naprostá většina z vás na první pohled uvidí, kde je problém. Ale předpokládejme, že ne. Jsme například unavení nebo nastal nějaký zkrat a my jsme si jistí, že -= je ten správný operátor. Zkusíme program přeložit - žádné chyby. Natěšení tedy program spustíme a co se nestane?

Výstup programu - Linux a programování v jazyce C

Chvilku na výsledek nechápavě hledíme... Suma čísel od 1 do 1000 přece není -34394132! Koukneme na kód, ale chybu nevidíme. Celé to pročítat a hledat chybu se nám nechce, takže spustíme debugger. Stačí napsat v terminálu gdb soubor. GDB vypíše nějaké informace o sobě a čeká na další příkazy.

Spuštění gdb - Linux a programování v jazyce C

GDB můžeme ukončit zadáním klasického "q" nebo "quit". Zadáním "help" se nám zobrazí témata nápovědy. Když napíšeme help téma (např. help breakpoints), vypíší se nám konkrétní příkazy a jejich význam. Program spustíme jednoduše napsáním "run". Kdybychom to ale teď udělali, tak jen proběhne, ukáže nám výstup a návratovou hodnotu, ale nic dalšího se nedozvíme. Nastavíme tedy breakpoint a spustíme program.

break main
run

Jak vidíte, breakpoint můžeme snadno nastavit na začátek funkce. Program poběží, než se dostane k breakpointu (v našem případě než je zavolána funkce main) a tam se zastaví a vypíše řádek, který bude zpracovávat. Nyní můžeme vypsat obsah proměnné příkazem display. Zkusíme to display sum. Vidíme, že proměnná obsahuje nějakou náhodnou hodnotu - ještě se neprovedlo přiřazení nuly. Krok provedeme příkazem n (zkratka pro next). Vidíme, že se vypsal další řádek (číslo na začátku je číslo řádku) a pod ním hodnota proměnné sum.

Výpis proměnné v gdb - Linux a programování v jazyce C

Vidíme, že příkaz display sum ve skutečnosti zapíná zobrazování proměnné, nemusíme ho tedy psát pokaždé znovu. Vykonáme další řádek - teď by měla být inicializovaná proměnná i. Pro jistotu si ji zkontrolujeme (display i). Hned vidíme problém - i by mělo být nula, ale je -1000. Pro zajímavost můžeme pokračovat dál.

gdb – nalezení chyby - Linux a programování v jazyce C

Vidíme, že suma se počítá správně a že k i je sice s každou iterací přičtena jednička, ale zároveň je odečten tisíc. Vypneme debugger, chvíli budeme nechápavě kroutit hlavou nad tím, jak jsme mohli udělat takovou chybu a nakonec ji opravíme. Hotovo, problém vyřešen. :)

GDB toho umí samozřejmě mnohem mnohem více, ale pro ukázku to stačí. Ještě uvedu několik užitečných příkazů a podíváme se na DDD.

  • list název_fce - vypíše zdrojový kód funkce
  • list 10,50 - vypíše řádky 10 až 50
  • print proměnná - pokud chceme jen jednou vypsat proměnnou a nechceme ji hned sledovat
  • info display - zobrazí, které proměnné sledujeme
  • undisplay číslo - zruší sledování, číslo získáme pomocí info display
  • continue - program pokračuje do dalšího breakpointu (pokud není, tak do konce)

DDD

Dále se krátce podíváme na DDD (Data Display Debugger), což je vlastně jen grafická nástavba pro debugger. Kromě GDB umí DDD pracovat s množstvím dalších a to je také jedna z jeho hlavních výhod. Je ale dnes již poněkud zastaralý (například chybějící podpora UTF-8), ale stále hojně užívaný a dobrá ukázka. V některém budoucím článku se podíváme na Nemiver, což je pěkná, moderní nástavba pro GDB, určená specificky pro GNOME. Uživatelé KDE, se mohou podívat například na KDbg. Další alternativou může být například Insight.

V DDD můžeme používat stejné příkazy (a klidně je zadávat ručně), ale můžeme také využít připravené GUI. Výhoda může být, že neustále vidíme svůj kód a nemusíme znát všechny příkazy nazpaměť. Takhle nějak vypadá DDD po spuštění (ddd program).

DDD – přehled - Linux a programování v jazyce C

Tak... Podíváme se na nějaké základní ovládání. Breakpoint můžeme vytvořit kliknutím (a držením) pravého tlačítka na řádek.

DDD – vytvoření breakpointu - Linux a programování v jazyce C

Jednoduché, že? Všimněme si, že na řádku se nám objevila krásná "stopka". Když na ní klikneme (zas pravým tlačítkem), můžeme ji odstranit, vypnout nebo případně otevřít vlastnosti (tam můžeme nastavit například podmínku - o těch ale v tomto díle mluvit nebudeme).

DDD – vypnutí breaku - Linux a programování v jazyce C

Program spustíme tlačítkem run. Všimněme si, že v příkazové řádce ve spodní části obrazovky se objevují stejné výpisy, jako jsme viděli při práci s GDB. Také se objevila sympaticky zelená šipka, která ukazuje na řádek, který má být vykonán.

DDD – běh programu - Linux a programování v jazyce C

Dále nás bude určitě zajímat, jak sledovat proměnnou. Opět je to jednoduché - stačí na ni kliknout pravým tlačítkem a vybrat si příslušný příkaz. Já se rozhodl, že ji chci sledovat pomocí display. Nahoře se objevila nová část okna a v té má proměnná.

DDD – sledování proměnné - Linux a programování v jazyce C

Nyní můžeme krokovat programem. Já udělal několik kroků, ale pak jsem si řekl, že se mi opravdu nechce proklikávat se celým cyklem (který bude mít o hodně více než původně zamýšlených 1000 iterací). Nastavil jsem si tedy další breakpoint (na řádek za cyklem).

DDD – continue - Linux a programování v jazyce C

Teď jen klikneme na cont (continue) a program poběží do dalšího breakpointu. V sum se nám ukázala finální hodnota. Když teď vybereme next, proběhne printf. Když bychom vybrali znovu cont, program proběhne až do konce.

DDD – dokončení programu - Linux a programování v jazyce C

A to je pro dnešek vše.

V příští lekci, Céčko a Linux - Code::Blocks, se podíváme na práci v Code::Blocks. K pokročilejším debuggovacím technikám se vrátíme v některém z budoucích dílů.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 34x (719 B)
Aplikace je včetně zdrojových kódů v jazyce C

 

Předchozí článek
Céčko a Linux – Makefile
Všechny články v sekci
Linux a programování v jazyce C
Přeskočit článek
(nedoporučujeme)
Céčko a Linux - Code::Blocks
Článek pro vás napsal David Novák
Avatar
Uživatelské hodnocení:
14 hlasů
Autor se zajímá především o nízkoúrovňové programování (C/C++, ASM) a návrh hardwaru (VHDL).
Aktivity