Terug naar de inhoudsopgave

Les 7 – Problemen opsporen met de debugger

Fouten opsporen

Fouten: computerprogramma's barsten ervan (soms vrij letterlijk). Eén van de bezigheden tijdens het programmeren is het opsporen van fouten in je broncode. Dit kan lastig en tijdrovend werk zijn, maar gelukkig is er een gereedschap dat het allemaal een stuk makkelijker voor ons maakt: de debugger.

Elke IDE gebruikt een andere debugger. Hoewel de meeste debuggers ongeveer dezelfde functies ondersteunen, zijn de details - bijvoorbeeld de sneltoetsen - vaak net iets anders. Deze les gaat uit van de debugger van Borland.

Een programma stap-voor-stap uitvoeren

Met de debugger kun je een programma in stappen uitvoeren. We nemen het onderstaande programma als uitgangspunt.

/*
    Programmeur:  dhr. Ronkes Agerbeek
    Programma:    Fouten opsporen

    Omschrijving: Een programma fol vouten.
*/

#include <iostream>

using namespace std;

// vermenigvuldigt twee getallen met elkaar
// getal1: het eerste getal om mee te vermenigvuldigen
// getal2: het tweede getal om mee te vermenigvuldigen
// returns: het product van de parameters
int Vermenigvuldig(int getal1, int getal2)
{
    // bereken het antwoord
    int myAnswer = getal1 + getal2;

    // geef antwoord terug
    return myAnswer;
}

// het programma start hier
void main()
{
    // vermenigvuldig twee getallen
    cout << Vermenigvuldig(4, 3);
}

Druk op F8 om dit programma te starten. Je ziet nu het venster van het programma verschijnen, maar het programma staat stil. Bovendien laat de IDE je code zien.

De code van je programma als je aan het debuggen bent.
De code van je programma als je aan het debuggen bent.

Het groene pijltje in de linkerkantlijn geeft aan welke stap de debugger straks gaat uitvoeren. In dit geval start de debugger in main. Druk op F8 deze stap te nemen.

Het pijltje springt nu naar de volgende statement. Merk op dat de debugger de accolade en het commentaar overslaat. Dit is code die de debugger niet kan uitvoeren, dus hij staat er ook niet bij stil.

De volgende opdracht voert een berekening uit en schrijf het antwoord naar het scherm. Voer deze opdracht ook uit met een druk op F8. Als je nu naar je venster kijkt, zie je dat het antwoord op het scherm staat.

De uitvoer van het programma.
De uitvoer van het programma (nou nou, spannend hoor).

Druk nog een keer op F8 om het programma af te maken en te sluiten.

Functies binnenstappen

Het is je misschien opgevallen dat de functie Vermenigvuldig wel is uitgevoerd, maar dat we er niet doorheen zijn gelopen. Start het programma nogmaals met F8 en druk nog een keer op F8 om main te starten. Het groene pijltje staat nu weer voor het statement dat de berekening uitvoert.

Druk nu op F7. Het groene pijltje springt naar het begin van de functie en nu kunnen we met F8 door de functie heen lopen.

We kunnen een functie binnenstappen.
We kunnen een functie binnenstappen.

Kortom, met F8 stap je over een statement heen, met F7 stap je een statement in. Als je een statement niet in kunt stappen - int Answer = getal1 + getal2; valt niet echt binnen te stappen - dan gedraagt F7 zich net zo als F8.

Breakpoints

Zeker bij grote programma's is het niet prettig als we telkens weer door het hele programma heen moeten lopen. Daarom kun je aangeven waar een programma moet stoppen zodat je de debugger kunt gebruiken. Zo'n punt waar het programma moet stoppen, noemen we een breakpoint.

Zet de cursor op de return-statement en druk op F5. Je ziet nu een rode stip voor de regel verschijnen. Deze stip geeft aan dat je hier een breakpoint hebt geplaatst.

Een rode stip geeft een breakpoint aan.
Een rode stip geeft een breakpoint aan.

Start nu het programma zoals je dat normaal gesproken zou doen, dus met F9. Het programma start, maar houdt halverwege op. De groene pijl staat nu op de regel waar je een breakpoint hebt ingesteld. Je kunt nu het programma verder stap voor stap uitvoeren of je kunt de rest van het programma uitvoeren door weer op F9 te drukken.

Je kunt het breakpoint verwijderen door je cursor weer op de regel te plaatsen en nogmaals op F5 te drukken.

Variabelen in de gaten houden

Nu we een programma op elk moment kunnen laten stoppen, gaan we eens kijken hoe we met de debugger informatie over ons programma op kunnen vragen. Plaats een breakpoint op het eerste statement van de functie Vermenigvuldig en start het programma.

In deze functie wordt de vermenigvuldiging uitgevoerd, maar ergens gaat het fout. Om de fout op te sporen, willen we de waarde van myAnswer in de gaten houden, terwijl we de functie stap-voor-stap uitvoeren. Dit kunnen we doen door een zogenaamde watch toe te voegen. Druk op Ctrl-Alt-W om het Watch venster tevoorschijn te halen.

Het (lege) Watch venster.
Het (lege) Watch venster.

Dubbelklik in het Watch venster. Je krijgt nu een dialoogvenster voor je waarin je aan kunt geven welke variabele je bij wilt houden. Typ in het vak Expression de naam myAnswer. myAnswer verschijnt nu in het Watch venster.

Big brother is watching myAnswer.
Big brother is watching myAnswer.

Het eerstvolgende statement zal de bereking uitvoeren. Druk of F8 en let op wat er in het Watch venster gebeurt. Je ziet de waarde van myAnswer verspringen. Je kunt ook meteen zien dat de waarde niet is wat hij moet zijn. Hier zit dus de fout.

Je kunt een watch eventueel verwijderen door erop te klikken in het Watch-venster en op Delete te drukken.

Context bepalen

Als we ergens een breakpoint plaatsen, dan weten we natuurlijk precies in welke functie we zitten als het programma stil staat. Meestal willen we niet alleen weten in welke functie we zitten, maar ook welke andere functie die functie heeft aangeroepen. We nemen even een ander voorbeeld.

/*
    Programmeur:  dhr. Ronkes Agerbeek
    Programma:    Context

    Omschrijving: Als er maar functies in aangeroepen worden.
*/

#include <iostream>

using namespace std;

// berekent de afgelegde afstand als beginsnelheid, versnelling en duur gegeven
// zijn
// speed: de beginsnelheid
// acceleration: de versnelling
// duration: de tijd dat de versnelling duurt
// returns: de afgelegde afstand
float Race(float speed, float acceleration, float duration)
{
    // bereken afstand
    float myAnswer = speed * duration + 0.5 * acceleration * duration * duration;

    // geef antwoord terug
    return myAnswer;
}

// vraagt om snelheid en acceleratie van Schumacher en berekent afgelegde
// afstand
// lengte: de duur van de race
// returns: de afstand die Schumacher heeft afgelegd
float Schumacher(float duration)
{
    // declareer variabelen
    float mySchumacherSpeed, mySchumacherAcceleration, mySchumacherDistance;

    // vraag om beginsnelheid van Schumacher
    cout << "Wat is de beginsnelheid van Schumacher? ";
    cin >> mySchumacherSpeed;

    // vraag om versnelling van Schumacher
    cout << "Wat is de versnelling van Schumacher? ";
    cin >> mySchumacherAcceleration;

    return Race(mySchumacherSpeed, mySchumacherAcceleration, duration);
}

// vraagt om snelheid en acceleratie van Montoya en berekent afgelegde
// afstand
// lengte: de duur van de race
// returns: de afstand die Montoya heeft afgelegd
float Montoya(float duration)
{
    // declareer variabelen
    float myMontoyaSpeed, myMontoyaAcceleration, myMontoyaDistance;

    // vraag om beginsnelheid van Montoya
    cout << "Wat is de beginsnelheid van Montoya? ";
    cin >> myMontoyaSpeed;

    // vraag om versnelling van Montoya
    cout << "Wat is de versnelling van Montoya? ";
    cin >> myMontoyaAcceleration;

    return Race(myMontoyaSpeed, myMontoyaAcceleration, duration);
}

// het programma start hier
void main()
{
    // declareer variabele
    float mySchumacherResult, myMontoyaResult;
    float myRaceTime;

    // vraag om lengte (tijd) van de race
    cout << "Hoe lang duurt de race? ";
    cin >> myRaceTime;

    // bereken afstand die Schumacher aflegt
    mySchumacherResult = Schumacher(myRaceTime);

    // bereken afstand die Monotoya aflegt
    myMontoyaResult = Montoya(myRaceTime);

    // is Schumacher verder gekomen?
    if (mySchumacherResult > myMontoyaResult)
    {
        // ja
        cout << "Schumacher wint." << endl;
    }

    // is Montoya verder gekomen?
    if (myMontoyaResult > mySchumacherResult)
    {
        // ja
        cout << "Montoya wint." << endl;
    }

    // zijn ze allebei even ver gekomen?
    if (myMontoyaResult == mySchumacherResult)
    {
        // ja
        cout << "Schumacher en Montoya zijn gelijk geëindigd." << endl;
    }

    // wacht op enter
    system("pause");
}
	

In bovenstaand programma wordt de functie Race twee keer aangeroepen: één keer vanuit Schumacher en één keer vanuit Montoya. Zet een breakpoint op het eerste statement van de functie Race. Start het programma en voer het uit totdat je bij het breakpoint komt.

Je bent nu in de functie Race belandt, maar hoe ben je daar nou eigenlijk gekomen? Dit kun je zien met behulp van de Call Stack (ook wel Stack Trace genoemd). Druk op Ctrl-Alt-S om de Call Stack op te vragen.

De Call Stack.
De Call Stack.

Je ziet een lijst met functienamen. De bovenste functie is de functie waar je nu inzit, in dit geval Race. De functie daar meteen onder, is de functie die Race heeft aangeroepen. Je ziet dat dat de functie Schumacher is. Daar weer onder ziet je dat de functie Schumacher is aangeroepen door main.

Druk nu op F9 om het programma voort te zetten. Even later kom je weer bij het breakpoint in de functie Race terecht. Als je nu kijkt naar de Call Stack, dan zie je dat Race dit keer is aangeroepen door de functie Montoya.

Bij de les