Valgrind-harjoitus

Tavoite: Opin käyttämään valgrind-työkalua muistinhallinnan ongelmien jäljittämiseen ja tulkitsemaan tämän työkalun tulosteita.

Tämä on tärkeää siksi, että lopuissa projekteissa yksi läpäisyvaatimus on, että ohjelmassa ei ole muistin käsittelyyn liittyviä ongelmia. Siitä on tarkoitus varmistua ohjelman testausvaiheessa valgrind-työkalun avulla.

Ohjeita: Ota esille ohjelmakoodipohja: wk04_trees/valgrind.

Tutkitaan seuraavaa pientä ohjelmaa, josta jo pintapuolisesti tutkimalla huomataan useita muistin käsittelyyn liittyviä ongelmatilanteita:

#include <iostream>

using namespace std;

int main() {
    int number1;
    int number2 = 111;
    int *ptr1 = new int;
    int *ptr2 = new int(222);

    cout << number1 << " "
         << number2 << " "
         << *ptr1 << " "
         << *ptr2 << endl;

    delete ptr1;

    *ptr1 = 333;
}

Tehtävässä vertaillaan valgrind-työkalun käyttöä Qt Creatorissa ja komentorivillä.

Komennon valgrind suorittaminen Qt Creatorissa

Avaa wk04_trees/valgrind projekti Qt Creatorissa.

Jos haluat, voit suorittaa ohjelman. Application Output -ikkunaan pitäisi tulostua “exited with code 0”, eli ohjelman suoritus päättyi paluuarvolla EXIT_SUCCESS, mikä tarkoitaa, että kaikki meni hyvin (siitä huolimatta, että ohjelmassa ei oikeastaan ole mitään muuta kuin virheitä). Huomaa, että kun ohjelmassa on näin paljon muistinkäsittelyyn liittyviä ongelmia kuin tässä esimerkkiohjelmassa, niin ei ole mitenkään taattua, että sen suorittaminen sujuu ongelmitta. Tehtävää laadittaessa ohjelman on pystynyt suorittamaan loppuun asti linux-desktopille asennetulla kääntäjäversiolla, mutta emme voi taata, että ohjelma toimii samoin kaikissa ympäristöissä.

Suorita valgrind valitsemalla Analyze > Valgrind Memory Analyzer. Ohjelma käynnistyy, mutta tällä kertaa Application Output -ikkunaan tulostuu “Analyzing finished”.

Lisäksi Qt Creatorissa aukeaa ikkuna Memcheck, jonka mustassa yläpalkissa pitäisi lukea “Memory Analyzer Tool finished, 8 issues were found”. Tässä tehtävässä tutkimme tämän ikkunan sisältöä. Jos Qt Creatorisi ei näytä otsikoiden Issue ja Location alla mitään, klikkaa ikkunan mustassa yläpalkissa olevaa filtteriä esittävää kuvaketta ja rastita kohta “External Errors”, niin valgrind-työkalun löytämät 8 ongelmaa ilmestyvät listaan.

Komennon valgrind suorittaminen komentorivillä

Käynnistä komentorivikäyttöliittymä ja navigoi hakemistoon, jossa em. ohjelmakoodi on talletettuna (edellisen kohdan projektihakemisto).

  1. Käännä lähdekoodisi käsin syöttämällä Linux-komentoriville käännöskäsky:

    g++ -std=c++11 -Wall -g main.cc
    

    Jos lähdekoodissa ei ollut virheitä, syntyy tuloksena suoritettava ohjelma (konekielitiedosto) nimeltä a.out.

  2. Jos haluat, voit kokeilla ohjelman suorittamista kirjoittamalla Linux-komentoriville käskyn:

    ./a.out
    
  3. Suorita seuraavaksi valgrind-komento valmiiksi käännetylle ohjelmalle:

    valgrind --quiet --leak-check=full ./a.out
    

    Jos ohjelmassa ei ole muistinkäsittelyyn liittyviä ongelmia, näytölle ei tulostu mitään ylimääräistä oman ohjelmasi tulosteiden lisäksi.

    Koska tässä esimerkkiohjelmassa oli muistinkäsittelyongelmia, valgrind tulostaa käsittämättömän määrän informaatiota, jota alamme nyt tutkia. Sinun pitää katsoa tulostetta alusta alkaen, eli vieritä terminaalia ylöspäin niin pitkälle, että näet kirjoittamasi valgrind-komennon. (Vinkki: terminaali-ikkuna kannattaa myös venyttää mahdollisimman korkeaksi työskentelyn helpottamiseksi.)

Asettele Qt Creator ja terminaali-ikkuna näytölle rinnakkain niin, että näet molemmat yhtä aikaa. Tämä helpottaa työskentelyäsi.

Qt Creatorin tulosteen ja komentorivitulosteen erot

Valitaan ensin tutkittavaksi ongelma, jonka valgrind on otsikoinut “Use of uninitialised value of size 8”. Etsi tämä sekä Qt Creatorissa että komentorivillä näkyvästä tulosteesta.

Havaitset heti, että komentorivillä tulosteen jokaisen rivin alussa näkyy jotain tämäntyylistä “==6647==” ja joillakin riveillä lisäksi jotakin tämäntyylistä “at 0x4F31BE3:”. Qt Creatorissa näitä ei näytetä ollenkaan, joten voit päätellä, että ne eivät ole olennaisia tietoja ja voit lukea komentorivitulostetta siten, että et huomioi näitä ollenkaan.

Jos yllä mainittua eroa rivien alussa ei huomioida, onko valgrind-työkalun kertomissa tiedoissa jotakin olennaista eroa riippuen siitä, miten se on suoritettu?

Location-sarakkeen sisältö

Qt Creatorin Memcheck-ikkunassa on kaksi saraketta: Issue ja Location. Miten Location-sarakkeeseen pääsee käsiksi sen jälkeen, kun on alas päin osoittavaa nuolen kärkeä klikaten avannut jonkin pitkistä riveistä tutkiaksesi sen sisältöä?

Alustamattoman muuttujan käyttäminen

Työkalun valgrind virheilmoitukset eivät ole selkeimpiä mahdollisia, joten jatkossa ymmärrät niitä paremmin, kun olet tällaisessa yksinkertaisessa ohjelmapätkässä nähnyt, mitä mistäkin virheestä tulostuu.

Tutki siis ohjelmakoodia ja sen aiheuttamia virheilmoituksia, ja selvitä, minkälaisen virheilmoituksen valgrind antaa alustamattoman muuttujan käyttämisestä?

Muistin vapauttamatta jättäminen

Minkälaisen virheilmoituksen valgrind antaa muistin vapauttamatta jättämisestä?

Jo vapautetun muistin käyttäminen

Minkälaisen virheilmoituksen valgrind antaa jo vapautetun muistin käyttämisestä?

Komennon valgrind suorittaminen

Staattinen analyysi tarkoittaa sitä, että analysoidaan ohjelmakoodia ilman sen suorittamista. Dynaaminen analyysi taas tarkoittaa sitä, että analysoidaan ohjelman toimintaa tietyllä suorituskerralla (esimerkiksi tietyillä syötteillä). Dynaamisessa analyysissä ohjelmasta löytyvät vain ne virheet, jotka kyseisessä suorituksessa tulevat ilmi.

Mitä voit päätellä siitä, että Qt Creator käynnistää ohjelman suorituksen prosessi-ikkunassa aina, kun suoritat valgrind-analyysin ohjelmalle?

Detecting invalidated iterator

#include <iostream>
#include <vector>

int main() {
    std::vector<int> numbers = {10, 20, 30};
    auto it = numbers.begin() + 1; // Iterator points to the second element

    numbers.insert(numbers.begin(), 5); // Insert a new element at the beginning

    // Attempt to access the value pointed by the invalidated iterator
    int value = *it;

    return 0;
}

Yllä olevassa koodissa hankitaan iteraattori toiselle vektorielementille. Uusi elementti lisätään vektorin alkuun, mikä tekee iterattorin it mitättömäksi. Minkä tyyppinen Valgrindin havaitsema virhe todennäköisesti tapahtuu mitätöidyn iteraattorin käytön vuoksi?

Komentorivivivut

Suorita valgrind vielä uudelleen komentoriviltä. Tällä kertaa kirjoita komennoksi:

valgrind --verbose --leak-check=full ./a.out

eli vivun --quiet sijaan käytätkin vipua --verbose. Miten tämä vaikuttaa tulosteeseen?

Irreflexive requirements error

Aiheutuu:

Irreflexive requirements error

std::vector<std::pair<AffiliationID, Coord>> aff_coords;

inline bool operator<<(Coord c1, Coord c2)
{
    return (c1.x * c1.x + c1.y * c1.y <= c2.x * c2.x + c2.y * c2.y);
}


sort(aff_coords.begin(), aff_coords.end(),
  [](auto a, auto b) {return a.second << b.second;});

Kuinka korjaan?

Komentorivin käyttämisestä taas

Materiaaliosiossa 1.4.1 haastateltu ohjelmistoammattilainen kertoi: “Vakavassa ohjelmistokehityksessä tarvittavien build-työkalujen jotkin ominaisuudet ovat järkevästi saatavilla vain komentorivillä. Graafiset integroinnit ainakin helposti laahaavat perässä, jos niitä tulee ollenkaan.”

Mitä tämä käytännössä tarkoittaa? Esimerkiksi muistinhallinnan virheiden analysoinnin tapauksessa sitä, että kun valgrind aikanaan julkaistiin, sitä käytettiin vain komentorivillä. Qt Creatorin valgrind-liityntä on toteutettu vasta jossakin vaiheessa myöhemmin.

Firmat haluavat usein käyttää uusimpia työkaluja, joten ammattilaisen on tärkeää osata käyttää myös komentoriviä.

Lopuksi, kokeile vielä suorittaa Qt Creatorissa Analyze > Valgrind Memory Analyzer with GDB. Huomaat, että tämä käynnistää valgrind-työkalun debuggaustilassa, jolloin ohjelmakoodia voi askeltaa rivi kerrallaan, kuten debuggerissa yleensäkin. Nyt Application Output -ikkunasta löytyy samannäköinen tuloste kuin terminaali-ikkunastakin.

Kun lopetat, varmista, että debuggeri on suljettu!

Useimmiten Qt Creator siirtyy Edit-tilasta Debug-tilaan, kun suoritat komennon valgrind. Joskus voi käydä niin, että Qt Creator ei toimi näin, vaan Memcheck-ikkunan näkymiseksi sinun pitää itse siirtyä Edit-tilasta Debug-tilaan. Tämän “ominaisuuden” (?) ilmentyessä on hyvä ymmärtää, että valgrind suoritetaan Qt Creatorissa aina Debug-tilassa.