Programovanie (2) v Jave
1-INF-166, letný semester 2023/24

Prednášky · Pravidlá · Softvér · Testovač
· Vyučujúcich predmetu možno kontaktovať mailom na adresách uvedených na hlavnej stránke. Hromadná mailová adresa zo zimného semestra v letnom semestri nefunguje.
· JavaFX: cesta k adresáru lib je v počítačových učebniach /usr/share/openjfx/lib.


Valgrind: Rozdiel medzi revíziami

Z Programovanie
Skočit na navigaci Skočit na vyhledávání
Riadok 54: Riadok 54:
  
 
===Neinicializovaný smerník===
 
===Neinicializovaný smerník===
Nasledujúci program zapisuje do pamäte, na ktorú ukazuje smerník s neinicializovnaou hodnotou
+
Nasledujúci program zapisuje do pamäte, na ktorú ukazuje smerník s neinicializovanou hodnotou
 
<pre>
 
<pre>
 
#include <iostream>
 
#include <iostream>

Verzia zo dňa a času 10:32, 2. november 2022

  • C-čko pri použití polí a smerníkov nekontroluje, či ich používame správne
  • Chybou v programe sa nám teda ľahko môže stať, že čítame alebo píšeme mimo alokovanej pamäte
  • Takéto chyby sa niekedy ťažko hľadajú
  • V Linuxe nám na hľadanie takýchto chýb pomôže nástroj valgrind, ktorý môžete použiť aj na skúške
  • Vo Windows môžete použiť Dr. Memory

Spustenie programu v nástroji valgrind pri použití Kate

  • Na príkazovom riadku v editore Kate spúšťate váš program príkazom typu ./prog, kde prog.cpp je meno vášho súboru
  • Namiesto toho napíšete valgrind ./prog
  • Nástroj valgrind bude náš program pozorne sledovať a keď robí divné veci v pamäti, vypíše nám o tom správu
  • Aby boli tieto správy zrozumiteľnejšie (obsahovali čísla riadkov), lepšie je skompilovať program s prepínačom -g
  • Namiesto make prog teda napíšete g++ -g prog.cpp -o prog alebo ešte lepšie je zapnúť si aj varovania kompilátora
g++ -g -Wall prog.cpp -o prog


Spustenie programu v nástroji valgrind pri použití Netbeans

  • Keď v Netbeans spustíme nástroj Build (ikonka kladivka; spúšťa sa tiež automaticky pred spustením programu), Netbeans zavolá kompilátor a vytvorí spustiteľný súbor
  • Tento spustiteľný súbor nájdeme v adresári typu NetBeansProjects/meno_projektu/dist/Debug/GNU-Linux-x86/, volá sa rovnako ako projekt
  • V Linuxe si ho môžeme na príkazovom riadku spustiť aj mimo prostredia Netbeans, stačí napísať NetBeansProjects/meno_projektu/dist/Debug/GNU-Linux-x86/meno_projektu
  • Namiesto toho ho môžeme spustiť valgrind NetBeansProjects/meno_projektu/dist/Debug/GNU-Linux-x86/meno_projektu
  • Nástroj valgrind bude náš program pozorne sledovať a keď robí divné veci v pamäti, vypíše nám o tom správu


Ukážky chýb a výsledok z valgrind

Neinicializovaná premenná

Nasledujúci program vypisuje neinicializovanú premennú i

#include <iostream>
using namespace std;
int main(void) {
    int i; cout << i << endl;
}

Valgrind vypíše okrem iného

==25895== Conditional jump or move depends on uninitialised value(s)
==25895==    at 0x4F3CCAE: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==25895==    by 0x4F3CEDC: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==25895==    by 0x4F493F9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==25895==    by 0x40082C: main (prog.cpp:4)
==25895== 
==25895== Use of uninitialised value of size 8
==25895==    at 0x4F3BB13: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==25895==    by 0x4F3CCD9: std::ostreambuf_iterator<char, std::char_traits<char> > std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::_M_insert_int<long>(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==25895==    by 0x4F3CEDC: std::num_put<char, std::ostreambuf_iterator<char, std::char_traits<char> > >::do_put(std::ostreambuf_iterator<char, std::char_traits<char> >, std::ios_base&, char, long) const (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==25895==    by 0x4F493F9: std::ostream& std::ostream::_M_insert<long>(long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==25895==    by 0x40082C: main (prog.cpp:4)

Dôležitá informácia je, že chyba nastala na riadku 4 v programe prog.cpp

Neinicializovaný smerník

Nasledujúci program zapisuje do pamäte, na ktorú ukazuje smerník s neinicializovanou hodnotou

#include <iostream>
using namespace std;
int main(void) {
    int *p; 
    *p = 7; 
    cout << *p << endl;
}
==25923== Use of uninitialised value of size 8
==25923==    at 0x400822: main (prog.cpp:5)
==25923== 
==25923== Invalid write of size 4
==25923==    at 0x400822: main (prog.cpp:5)
==25923==  Address 0x0 is not stack'd, malloc'd or (recently) free'd
==25923== 
==25923== 
==25923== Process terminating with default action of signal 11 (SIGSEGV)
==25923==  Access not within mapped region at address 0x0
==25923==    at 0x400822: main (prog.cpp:5)
==25923==  If you believe this happened as a result of a stack
==25923==  overflow in your program's main thread (unlikely but
==25923==  possible), you can try to increase the size of the
==25923==  main thread stack using the --main-stacksize= flag.
==25923==  The main thread stack size used in this run was 8388608.

Chybné odalokovanie

Tento program sa pokúša odalokovať pamäť, ktorá nebola alokovaná

#include <iostream>
using namespace std;
int main(void) {
    int i = 7; 
    int *p = &i; 
    delete p;
}
==25952== Invalid free() / delete / delete[] / realloc()
==25952==    at 0x4C2F24B: operator delete(void*) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==25952==    by 0x4007A7: main (prog.cpp:6)
==25952==  Address 0xfff0002bc is on thread 1's stack
==25952==  in frame #1, created by main (prog.cpp:3)

Písanie za koniec poľa

Ani valgrind nemusí nájsť všetky chyby, napr. tu sa k premennej správame ako ku poľu a píšeme mimo, ale valgrind si to nevšimne:

#include <iostream>
using namespace std;
int main(void) {
    int i;
    int *p = &i; 
    for(int j = 0; j<2; j++) {
      p[j] = j;
    }
}

Ak index 2 nahradíme 200, valgrind už vypíše chybu...

  • Celkovo valgrind lepšie deteguje chyby týkajúce sa dynamicky alokovanej pamäte (pomocou new)

Hľadanie neodalokovanej pamäte

Valgrind nám tiež môže pomôcť nájsť pamäť, ktorú sme alokovali cez new, ale zabudli odalokovať cez delete alebo delete[].

  • V programe nižšie máme dve volania new, ku ktorým chýba odalokovanie
#include <iostream>
using namespace std;
int main(void) {
  int n = 100;
  int *a = new int[n];
  double *b = new double;
  for(int i = 0; i<n; i++) {
    a[i] = i;
  }
}

Valgrind vypíše

==7556== Memcheck, a memory error detector
==7556== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==7556== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==7556== Command: ./prog
==7556== 
==7556== 
==7556== HEAP SUMMARY:
==7556==     in use at exit: 73,112 bytes in 3 blocks
==7556==   total heap usage: 3 allocs, 0 frees, 73,112 bytes allocated
==7556== 
==7556== LEAK SUMMARY:
==7556==    definitely lost: 408 bytes in 2 blocks
==7556==    indirectly lost: 0 bytes in 0 blocks
==7556==      possibly lost: 0 bytes in 0 blocks
==7556==    still reachable: 72,704 bytes in 1 blocks
==7556==         suppressed: 0 bytes in 0 blocks
==7556== Rerun with --leak-check=full to see details of leaked memory
==7556== 
==7556== For counts of detected and suppressed errors, rerun with: -v
==7556== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
  • Vidíme teda, že počas beho programu sa alokovali 3 kusy pamäte a ani jeden sa neodalokoval
  • Z toho jedno alokovanie bolo v štandardnej knižnici, ale tie ďalšie dve sú naše a bolo by pekné ich odalokovať
  • Neodalokovaná pamäť v knižnici je označená ako "still reachable", takú môžete ignorovať. Zaujíma vás pamäť označená ako "definitely lost", prípadne "indirectly lost".
  • Podľa pokynov programu spustíme valgrind --leak-check=full ./prog
  • Pribudne podrobnejší rozbor neodalokovanej pamäte
==7565== HEAP SUMMARY:
==7565==     in use at exit: 73,112 bytes in 3 blocks
==7565==   total heap usage: 3 allocs, 0 frees, 73,112 bytes allocated
==7565== 
==7565== 8 bytes in 1 blocks are definitely lost in loss record 1 of 3
==7565==    at 0x4C2E0EF: operator new(unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7565==    by 0x40078B: main (prog.cpp:6)
==7565== 
==7565== 400 bytes in 1 blocks are definitely lost in loss record 2 of 3
==7565==    at 0x4C2E80F: operator new[](unsigned long) (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==7565==    by 0x40077D: main (prog.cpp:5)
==7565== 
  • valgrind nám teraz vypísal, na ktorom riadku je new, ku ktorému nebol volaný delete (riadky 5 a 6 v súbore prog.cpp)
  • tu to vidíme ľahko aj bez valgrind, ale vo väčšom programe vám táto informácia môže pomôcť

Cvičenie

Nasledujúci program by mal správne vypísať text "AhojAhojAhojAhoj", ale je v ňom zopár chýb. Skúste nájsť a opraviť chyby čítaním programu, použitím debugera, programu valgrind, prípadne si pridajte nejaké pomocné výpisy premenných.

  • v programe valgrind je vždy dobré začať od prvej vypísanej chyby, opraviť ju a spustiť valgrind znovu
#include <iostream>
using namespace std;

void opakuj(char kam[], char co[], char kolko) {
    /* Funkcia dostane na vstupe retazec co a cislo kolko a nakopiruje ho tolkokrat
     * za sebou do retazca kam. */

    int i=0; // pozicia v kam
    for(int opakovanie=0; opakovanie<kolko; opakovanie++) {  // opakuj kopirovanie
        for(int j=0; co[j]!=0; j++) {  // prechod cez znaky retazca co
            kam[i] = co[j];
            i++;
        }        
    }
}

int main(void) {
    char ahoj[4] = {'A', 'h', 'o', 'j'};
    char vysledok[16];  
    opakuj(vysledok, ahoj, 4);
    cout << vysledok << endl;
}