Testování

Nikdo není neomylný a chyby při programování jsou běžná věc (až moc). Při pogramování v C/C++ mají chyby tu ošklivou vlastnosti, že se často projeví neoprávněným přistupem do paměti nebo něčím podobným, co program odstřelí. Zjistit, kde je chyba, bývá velmi obtížné. V této kapitole vám ukáži pár nástrojů, které vám v tom mohou pomoci.

GDB

GDB (gnu debugger) je klasický nástroj na debuggování, tj. vyhledávání a odstraňování chyb.

Česky by se asi řeklo deratizace, nebo odvšivení :-) Bug znamená anglicky brouk. V angličtině se výraz bug pro chybu používá dlouho. Dříve totiž nebylo neobvyklé, že za chybou nějakého elektronického zařízení stál skutečně nějaký brouk :-).

Moderní vývojové prostředí mají obvykle nějaký ten debugger integrovaný v sobě, ale není na škodu vědět, že existuje i starý dobrý GDB a jak ho požít.

Ukázku GDB předvedu na tomto kódu:

  1. /*------------------------------------------------*/
  2. /* 08testovani/write.c                            */
  3.  
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <errno.h>
  10.  
  11. void zapisText(char * text) {
  12.     int fd;
  13.     FILE *file;
  14.     fd = open("pokus.txt", O_WRONLY | O_APPEND);
  15.     /* if(fd < 0) { fprintf(stderr,"Nepodarilo se otevrit %s\n", "pokus.txt"); exit(1); } */
  16.     file = fdopen(fd, "a");
  17.     /* if (f == NULL) { fprintf(stderr,"Nepodarilo se vytvorit FILE *\n"); perror(NULL); exit(1); } */
  18.     fprintf(file, "%s", text);
  19.     fclose(file);
  20. }
  21.  
  22. int main(int argc, char *argv[]) {
  23.     char * text = "Ahoj Svete!\n";
  24.     zapisText(text);
  25.     return 0;
  26. }
  27.  
  28. /*------------------------------------------------*/

V programu jsou zakomentované kontroly chyb, jako že jsem na ně zapoměl.

Aby vám byl program gdb co k čemu, musíte přeložit svůj testovaný program s volbou -g. Tato volba způsobí, že překladač do výsledného spustitelného souboru přidá nějaké ladící informace, které gdb využije k tomu, aby vám mohl ukázat, na jaké řádce ve zdrojovém kódu program havaroval, nebo abyste mohli program spouštět řádek po řádku (tzv. krokovat) …

$ clang -o write write.c -g -Wall

Teď spusťte gdb a jako argument mu předejte název programu, který chcete ladit.

$ gdb ./write
For help, type "help".
Type "apropos word" to search for commands related to "word".
..
Reading symbols from /home/petr/resource/c/linux/zdrojaky-linux/08testovani/write...done.
(gdb)

Program gdb teď čeká na vaše přikazy. Základním příkazem je help, kterým si můžete zobrazit nápovědu.

(gdb) help
List of classes of commands:

aliases -- Aliases of other commands
breakpoints -- Making program stop at certain points
data -- Examining data
files -- Specifying and examining files
internals -- Maintenance commands
obscure -- Obscure features
running -- Running the program
stack -- Examining the stack
status -- Status inquiries
support -- Support facilities
tracepoints -- Tracing of program execution without stopping the program
user-defined -- User-defined commands

Type "help" followed by a class name for a list of commands in that class.
Type "help all" for the list of all commands.
Type "help" followed by command name for full documentation.
Type "apropos word" to search for commands related to "word".
Command name abbreviations are allowed if unambiguous.
(gdb) help running
…
run -- Start debugged program
signal -- Continue program with the specified signal
start -- Run the debugged program until the beginning of the main procedure
step -- Step program until it reaches a different source line
…

Výstup nápovědy jsem zkrátil, protože je moc dlouhý. Jak se můžete dočíst, příkaz run spustí debugovaný program (v našem příkladě program write).

(gdb) run
Starting program: /home/petr/resource/c/linux/zdrojaky-linux/08testovani/write

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a74d2d in vfprintf () from /lib64/libc.so.6

Program se spustil, ale byl ukončen chybou Segmentation fault. Teď můžu použít gdb ke zjištění co se vlastně stalo.

Příkaz where zobrazí zásobník volání funkcí k místu, kde program přestal běžet.

(gdb) where
#0  0x00007ffff7a74d2d in vfprintf () from /lib64/libc.so.6
#1  0x00007ffff7a7f237 in fprintf () from /lib64/libc.so.6
#2  0x0000000000400691 in zapisText (text=0x400783 "Ahoj Svete!\n") at write.c:18
#3  0x00000000004006db in main (argc=1, argv=0x7fffffffdd48) at write.c:24

Z výpisu se dočtete, že poslední volaná funkce byla vprintf() z knihovny /lib64/libc.so.6. Tu vyvolala funke fprintf() z téže knihovny. A tu zase vyvolala funke zapisText() z našeho zdrojového kódu write.c na řádce 18.

Knihovna libc.so.6 nebyla přeložena s ladícími symboly, proto nevidíte zdrojový kód s řádkou, kde jsou funke vprintf() a fprintf() volány. Nevidíte ani argumenty, se kterými byli funkce volány.

U volání funkce zapisText() vidíte, jaký jí byl předán arugment. Je to adresa 0x400783, na které začíná řetězec Ahoj Svete!\n".

Příkazem list můžete vypsat zdrojový kód z místa, kde se zrovna nacházíte. Poslední zdrojový kód, který má gdb k dispozici je teď write.c a nacházíte se na řádce 18. Příkazem list vypíšete kus zdrojového kódu kolem tohoto řádku.

(gdb) list
14          fd = open("pokus.txt", O_WRONLY | O_APPEND);
15          /* if(fd < 0) { fprintf(stderr,"Nepodarilo se otevrit %s\n", "pokus.txt"); exit(1); } */
16          file = fdopen(fd, "a");
17          /* if (f == NULL) { fprintf(stderr,"Nepodarilo se vytvorit FILE *\n"); perror(NULL); exit(1); } */
18          fprintf(file, "%s", text);
19          fclose(file);
20      }
21
22      int main(int argc, char *argv[]) {
23          char * text = "Ahoj Svete!\n";

Z předchozího víte, že argument text ukazuje správně na "Ahoj světe!\n". Lze se tedy domnívat, že bude chyba v proměnné file. Můžete si zkusit vytisknout její hodnotu.

(gdb) print file
No symbol "file" in current context.

Hmm, něco je špatně. Asi se nenacházíte ve správném contextu.

(gdb) info frame
Stack level 0, frame at 0x7fffffffdb30:
 rip = 0x7ffff7a74d2d in vfprintf; saved rip 0x7ffff7a7f237
 called by frame at 0x7fffffffdc10
 Arglist at 0x7fffffffd560, args: 
 Locals at 0x7fffffffd560, Previous frame's sp is 0x7fffffffdb30
 Saved registers:
  rbx at 0x7fffffffdaf8, rbp at 0x7fffffffdb20, r12 at 0x7fffffffdb00, r13 at 0x7fffffffdb08, r14 at 0x7fffffffdb10, r15 at 0x7fffffffdb18,
  rip at 0x7fffffffdb28

Aha, jsme ve funkci vprintf() (Stack #0). Musíme se přesunout na místo, kde je volána fukce fprintf(), tj. do funkce zapisText(), tj o 2 levely výše.

(gdb) up 2
#2  0x0000000000400691 in zapisText (text=0x400783 "Ahoj Svete!\n") at write.c:18
18          fprintf(file, "%s", text);
(gdb) list
13          FILE *file;
14          fd = open("pokus.txt", O_WRONLY | O_APPEND);
15          /* if(fd < 0) { fprintf(stderr,"Nepodarilo se otevrit %s\n", "pokus.txt"); exit(1); } */
16          file = fdopen(fd, "a");
17          /* if (f == NULL) { fprintf(stderr,"Nepodarilo se vytvorit FILE *\n"); perror(NULL); exit(1); } */
18          fprintf(file, "%s", text);
19          fclose(file);
20      }
21
22      int main(int argc, char *argv[]) {
(gdb) print file
$1 = (FILE *) 0x0

Aha, a už je to jasné. Proměnná file je ukazatel typu FILE *, který ukazuje na 0x0, tedy na NULL.

file vrací fdopen(), tak schválně co on měl za argument (fd).

(gdb) print fd
$2 = -1

Nojo, fd byl -1. Je jasné, že funkce open() vrátila -1, což znamená, že se jí nepodařilo otevřít soubor. A to proto, že soubor neexistuje – ale na to už nepřijdete pomocí gdb, ale díky vašim odborným znalostem chování funkce open() :-).

Příkaz up má svého bratříčka down, kterým můžete jít zásobníkem volání zase dolů …

Breakpoints

Na stejném příkladu uvedu trochu jiný přístup hledání chyb. Řekněme že tušíte, že je chyba někde ve funkci zobrazText(). Můžete nastavit tzv. breakpoint, což je místo v kódu, kde gdb vykonávání programu zastaví a umožní vám prohlížet si proměnné atp.

Breakpoint můžete nastavovat různými způsoby, např. můžete určit na jakém řádku v jakém zdrojovém souboru se má vykonávání programu zastavit, nebo, jako v následujícím příkladě, zadáte název funkce, při jejímž volání se program zastaví.

$ gdb
GNU gdb (GDB; openSUSE 13.1) 7.6.50.20130731-cvs
...
For help, type "help".
Type "apropos word" to search for commands related to "word".

(gdb)

Nejdříve načtu program (protože jsem ho nepředal jako argument příkazové řádky), pak nastavím breakpoint a program spustím.

(gdb) file ./write
Reading symbols from /media/Documents/public_html/www/nette/install/resource/c/linux/zdrojaky-linux/08testovani/write...done.
(gdb) break zapisText
Breakpoint 1 at 0x400659: file write.c, line 14.
(gdb) run
Starting program: /home/petr/resource/c/linux/zdrojaky-linux/08testovani/write 

Breakpoint 1, zapisText (text=0x400783 "Ahoj Svete!\n") at write.c:14
14          fd = open("pokus.txt", O_WRONLY | O_APPEND);
(gdb) list
9
10
11      void zapisText(char * text) {
12          int fd;
13          FILE *file;
14          fd = open("pokus.txt", O_WRONLY | O_APPEND);
15          /* if(fd < 0) { fprintf(stderr,"Nepodarilo se otevrit %s\n", "pokus.txt"); exit(1); } */
16          file = fdopen(fd, "a");
17          /* if (f == NULL) { fprintf(stderr,"Nepodarilo se vytvorit FILE *\n"); perror(NULL); exit(1); } */
18          fprintf(file, "%s", text);
(gbd)

Program se zastavil na řádce 14. Příkazem next se tato řádka vykoná. (Příkazem step byste vstoupili do těla funkce open() a vyvolali byste první řádek v ní – pokud byste měli k dispozici zdrojové kódy k open()).

(gdb) next
16          file = fdopen(fd, "a");
(gdb)

Příkaz byl vykonán a program se zastavil na další řádce. Teď si můžete prohlédnout hodnotu proměnné fd a pokračovat v krokování programu.

(gdb) print fd
$1 = -1
(gdb) next
18          fprintf(file, "%s", text);
(gdb) print file
$2 = (FILE *) 0x0
(gdb) print text
$3 = 0x400760 "Ahoj Svete!\n"
(gdb) next

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a99232 in fputs () from /lib64/libc.so.6

Program gdb umí snad všechno, co si usmyslíte. Breakpointy můžete libovolně přidávat, zapínat, vypínat, mazat. Příkazem continue spustíte program, dokud nenarazí gdb na další zapnutý breakpoint (nebo program neskončí), můžete nastavit breakpoint s podmínkou (že se má aktivovat, jen když nějaká proměnná má nějakou hodnotu …) atd. Projdete-li si nápovědu (příkaz help) uvidíte sami, co všechno umí.

Programy nm, ltrace a strace

Program nm vypíše symboly z objektového souboru.

nm write
0000000000601058 B __bss_start
0000000000601058 b completed.6362
0000000000601048 W data_start
0000000000601048 D __data_start
0000000000400580 t deregister_tm_clones
00000000004005f0 t __do_global_dtors_aux
0000000000600e08 t __do_global_dtors_aux_fini_array_entry
0000000000601050 D __dso_handle
0000000000600e18 d _DYNAMIC
0000000000601058 D _edata
0000000000601060 B _end
                 U fclose@@GLIBC_2.2.5
                 U fdopen@@GLIBC_2.2.5
0000000000400744 T _fini
                 U fputs@@GLIBC_2.2.5
0000000000400610 t frame_dummy
0000000000600e00 t __frame_dummy_init_array_entry
00000000004008c0 r __FRAME_END__
0000000000601000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
00000000004004b8 T _init
0000000000600e08 t __init_array_end
0000000000600e00 t __init_array_start
0000000000400750 R _IO_stdin_used
                 w _ITM_deregisterTMCloneTable
                 w _ITM_registerTMCloneTable
0000000000600e10 d __JCR_END__
0000000000600e10 d __JCR_LIST__
                 w _Jv_RegisterClasses
0000000000400740 T __libc_csu_fini
00000000004006d0 T __libc_csu_init
                 U __libc_start_main@@GLIBC_2.2.5
0000000000400697 T main
                 U open@@GLIBC_2.2.5
00000000004005b0 t register_tm_clones
0000000000400550 T _start
0000000000601058 D __TMC_END__
0000000000400640 T zapisText

Ve výpisu najdete funkci main() i zapisText(), ale třeba i open() (z GLIBC_2.2.5), takže hned víte, že se program možná pokusí otevřít nějaký soubor. Že by to byl virus? :-).

Program ltrace spustí program a sleduje, jaké knihovní (library) funkce program používá. Sleduje taky signály a může sledovat systémová volání.

Program strace funguje podobně jako ltrace, zaznamenává systémová volání spuštěného programu.

$ ltrace ./write
__libc_start_main(0x400697, 1, 0x7fff80e830e8, 0x4006d0 <unfinished ...>
open("pokus.txt", 1025, 020072030370)                                                   = -1
fdopen(0xffffffff, 0x40075e, 0x7fff80e830f8, -112)                                      = 0
fputs("Ahoj Svete!\n", 0 <no return ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++
$ strace ./write
execve("./write", ["./write"], [/* 97 vars */]) = 0
brk(0)                                  = 0x22bf000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3a4bb20000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=119213, ...}) = 0
mmap(NULL, 119213, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f3a4baf8000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\360\34\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2008128, ...}) = 0
mmap(NULL, 3861056, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3a4b548000
mprotect(0x7f3a4b6ed000, 2097152, PROT_NONE) = 0
mmap(0x7f3a4b8ed000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a5000) = 0x7f3a4b8ed000
mmap(0x7f3a4b8f3000, 14912, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3a4b8f3000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3a4bb1f000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3a4bb1e000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3a4bb1d000
arch_prctl(ARCH_SET_FS, 0x7f3a4bb1e700) = 0
mprotect(0x7f3a4b8ed000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f3a4bb17000, 4096, PROT_READ) = 0
munmap(0x7f3a4baf8000, 119213)          = 0
open("pokus.txt", O_WRONLY|O_APPEND)    = -1 ENOENT (No such file or directory)
fcntl(-1, F_GETFL)                      = -1 EBADF (Bad file descriptor)
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_MAPERR, si_addr=0} ---
+++ killed by SIGSEGV +++
Neoprávněný přístup do paměti (SIGSEGV)

To co vidíte za rovnítkem na konci volání funkce je její návratová hodnota. A někde k tomu máte navíc popis jejího významu.

Program strace skončil trochu předčasně, protože se pokusil zjistit nějaké informace o deskriptoru -1 pomocí volání funkce fcntl()

Program strip

Výsledný program obsahuje kromě instrukcí také symboly, které se mohou hodit např. pro ladění programu, informace o překladači, o zdrojovém kódu atp. A to nejen když použijete volbu -g. Některé z těchto pro běh programu zbytečných informací lze odstranit pomocí programu strip, zmenšit tak program a tím jej zrychlit.

ls -l ./write
-rwxrwxrwx 1 root root 14614 17. srp 21.49 write
$ strip ./write
$ ls -l ./write
-rwxrwxrwx 1 root root 6360 17. srp 21.55 write

Program write je od této chvíle pro ladění nepoužitelný.

Kdybyste byli jó zvědaví, podívejte se na Strip command examples.

Program mtrace

Program mtrace vám pomůže odhalit úniky paměti (paměť, kterou jste alokovali, ale neuvolnili).

Aby program mtrace fungoval, musíte přidat do zdojového kódu volání funkce mtrace() deklarované v hlavičkovém souboru <mcheck.h>. Tato funkce zapne logování volání funkcí malloc(), realloc() a free(). Funkce muntrace() to zase vypne (takže můžete logovat jen určitou část programu, když chcete.)

  1. /*------------------------------------------------*/
  2. /* 08testovani/mtrace.c                           */
  3.  
  4. #include <stdlib.h>
  5. #include <string.h> /* strncpy() */
  6. #include <mcheck.h>
  7.  
  8. #define N 5
  9. #define M 10
  10.  
  11. int main(void) {
  12.         char **pole;
  13.         int i;
  14.         mtrace();
  15.  
  16.         pole = (char **) malloc(N * sizeof(char *));
  17.         if (pole == NULL) {
  18.                 return 1; /* error */
  19.         }
  20.  
  21.         for(i = 0; i < N; i++) {
  22.                 pole[i] = (char *) malloc(M*sizeof(char));
  23.                 strncpy(pole[i],"Hello World\n", M);
  24.         }
  25.  
  26.         /* tady by se melo zase v cyklu uvolnit kazde pole[i] */
  27.         free(pole);
  28.         muntrace();
  29.  
  30.         return 0;
  31. }
  32.  
  33. /*------------------------------------------------*/

Překlad (s volbou -g):

$ clang -o mtracetest mtrace.c -g -Wall

Aby sledovaný program vytvořil nějaký výstup pro program mtrace, musíte nastasvit proměnnou prostředí MALLOC_TRACE.

$ export MALLOC_TRACE=mtrace-output.txt
$ ./mtracetest # spoustim muj program
$ ls *.txt
mtrace-output.txt
$ mtrace ./mtracetest mtrace-output.txt
Memory not freed:
-----------------
           Address     Size     Caller
0x0000000000e5c490      0xa  at /home/petr/resource/c/linux/zdrojaky-linux/08testovani/mtrace.c:22
0x0000000000e5c4b0      0xa  at /home/petr/resource/c/linux/zdrojaky-linux/08testovani/mtrace.c:22
0x0000000000e5c4d0      0xa  at /home/petr/resource/c/linux/zdrojaky-linux/08testovani/mtrace.c:22
0x0000000000e5c4f0      0xa  at /home/petr/resource/c/linux/zdrojaky-linux/08testovani/mtrace.c:22
0x0000000000e5c510      0xa  at /home/petr/resource/c/linux/zdrojaky-linux/08testovani/mtrace.c:22

Ve výstupu vidíte adresu alokované a neuvolněné paměti, její velikost (0xa = 10 bajtů, což odpovídá 10xsizeof(char)). A taky vidíte číslo řádky zrojového souboru, kde byla paměť alokována.

Program gprof

Program grpof je profiler, který sleduje kolikrát byla jaká funkce spuštěna a jak dlouho její běh trval.

gprof analyzuje výstup, který vytvoří program přeložený s volbou -pg.

V testovacím programu najdete funkci factorial(), která v cyklu provádí nějaký zybtečný výpočet, abych ji trochu pozdržel.

  1. /*------------------------------------------------*/
  2. /* 08testovani/gproftest.c                        */
  3.  
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6.  
  7. int nactiCislo() {
  8.         int fact;
  9.         printf("Zadejte číslo pro výpočet faktoriálu: ");
  10.         fflush(stdout);
  11.         scanf("%i",&fact);
  12.         return fact;
  13. }
  14.  
  15. long double factorial(long double f) {
  16.         unsigned int i, k = 0, l = 100000000;
  17.         for(i = 0;  i < l; i++) {
  18.                 k =  i + 1; k--;
  19.         }
  20.         if (f < 0) return 0;
  21.         if (f == 1) return 1;
  22.         return f*factorial(f-1);
  23. }
  24.  
  25. int main(void) {
  26.         int fact;
  27.  
  28.         fact = nactiCislo();
  29.         printf("Výsledek je %.0Lf\n", factorial((long double) fact));
  30.  
  31.         return 0;
  32. }
  33.  
  34. /*------------------------------------------------*/

Profilovací data se ukládají do souboru gmon.out.
Překladač clang bohužel (zatím?) -pg neumí, takže použiji gcc. Překlad, spuštění a profilování programu:

$ gcc -o gproftest gproftest.c -pg -Wall
$ ./gproftest
Zadejte číslo pro výpočet faktoriálu: 10
Výsledek je 3628800
$ gprof -b ./gproftest
Flat profile:

Each sample counts as 0.01 seconds.
  %   cumulative   self              self     total          
 time   seconds   seconds    calls   s/call   s/call  name    
101.03     43.92    43.92        1    43.92    43.92  factorial
  0.00     43.92     0.00        1     0.00     0.00  nactiCislo


                        Call graph


granularity: each sample hit covers 2 byte(s) for 0.02% of 43.92 seconds

index % time    self  children    called     name
                                   9             factorial [1]
               43.92    0.00       1/1           main [2]
[1]    100.0   43.92    0.00       1+9       factorial [1]
                                   9             factorial [1]
-----------------------------------------------
                                                 <spontaneous>
[2]    100.0    0.00   43.92                 main [2]
               43.92    0.00       1/1           factorial [1]
                0.00    0.00       1/1           nactiCislo [3]
-----------------------------------------------
                0.00    0.00       1/1           main [2]
[3]      0.0    0.00    0.00       1         nactiCislo [3]
-----------------------------------------------


Index by function name

   [1] factorial               [3] nactiCislo

Pokud nepoužijte volbu -b, bude výstup prošpikován nápovědou (popisem toho, co co znamená). Vyzkoušejte si to.

Z výstupu vidíte, že funkce factorial() byla volána z funkce factorial() 9x a z funkce main() 1x. Což by pro fakoriál 10 dávalo smysl :). Taky vidíte, že funkce factorial() volala funkci faktorial() 9x.

V další části tabulky vidíte, že funkce main() volala funkce factorial() a nactiCislo(). Protože funkce main() volala funkci factorial(), vidíte, že její „děti“ (funkce, které volala, tedy hlavně factorial()) zabrali celkem čas 43.92 vteřiny.

Poslední část ukazuje, že funke natiCislo() byla volána z funkce main().

Všechno proběhlo tak rychle, že jsou všude výsledné časy volání 0.0 sekund, až na funkci factorial(), ve které program strávil celkem 43.92 vteřiny.

Všiměte si, že se do délky volání funkce nactiCislo() nezapočítal čas, kdy funke čekala na uživatelský vstup.

Program valgrind

Valgrind je další nástroj pro analýzu. Valgrind umí profilovat program, zjišťovat úniky paměti, hledat chyby v multithreadových aplikacích a mnoho dalšího.

Další info viz jeho domovská stránka. Začít můžete tutoriálem.

$ valgrind --leak-check=yes ./mtrace
...
==4128== 50 bytes in 5 blocks are definitely lost in loss record 1 of 2
==4128==    at 0x4C277AB: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==4128==    by 0x400691: main (in /home/petr/resource/c/linux/zdrojaky-linux/08testovani/mtrace)
==4128==
...

Valgrind, na rozdíl od mtrace, nevyžaduje úpravu zdrojového kódu (volání funkce mtrace()).

Komentář Hlášení chyby
Created: 17.8.2014
Last updated: 15.11.2015
Tato stánka používá ke svému běhu cookies, díky kterým je možné monitorovat, co tu provádíte (ne že bych to bez cookies nezvládl). Také vás tu bude špehovat google analytics. Jestli si myslíte, že je to problém, vypněte si cookies ve vašem prohlížeči, nebo odejděte a už se nevracejte :-). Prohlížením tohoto webu souhlasíte s používáním cookies. Dozvědět se více..