3. díl - Céčko a Linux - Debugging

C++ Linux Céčko a Linux - Debugging

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

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

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

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

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

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

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

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

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é

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

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

A to je pro dnešek vše.. Příště 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ů.


 

Stáhnout

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

 

  Aktivity (2)

Článek pro vás napsal David Novák
Avatar
Autor v současné době studuje FIT VUT Brno a zajímá se především o nízkoúrovňové programování (C/C++, ASM) a návrh hardwaru (VHDL). Je zde také členem výzkumného týmu ANT@FIT (Accelerated Network Technologies).

Jak se ti líbí článek?
Celkem (6 hlasů) :
4.833334.833334.833334.833334.83333


 


Miniatura
Předchozí článek
Céčko a Linux – Makefile
Miniatura
Všechny články v sekci
Programování v jazyce C v Linuxu
Miniatura
Následující článek
Céčko a Linux - Code::Blocks

 

 

Komentáře

Avatar
Libor Šimo (libcosenior):

Pekný článok.

Odpovědět 3.3.2015 15:25
Aj tisícmíľová cesta musí začať jednoduchým krokom.
Avatar
David Novák
Tým ITnetwork
Avatar
Odpovědět  +1 3.3.2015 15:39
Chyba je mezi klávesnicí a židlí.
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 2 zpráv z 2.