Minne og kodesporing
- Minne
- En variabel endrer seg
- En funksjon blir kalt
- Debuggeren
- Manuell kodesporing med tabell
- Manuell kodesporing av funksjoner med tabell
Å lese kode og forstå hva som vil skje om den kjøres er en helt essensiell ferdighet for en programmerer. I dette notatet skal vi forsøke å bygge en intuisjon for hva som egentlig skjer i datamaskinen når et program kjører. Å manuelt forutsi hvordan «minnet» vil endre seg steg for steg, kaller vi kodesporing.
Minne
Kildekoden til et program består av en sekvens av setninger (statements) som skal utføres én etter én i rekkefølge. Dataprogrammet som leser kildekoden og utfører setningene kalles en fortolker.
En fortolker innholder i hovedsak tre bevegelige deler som vil endre tilstand for hvert steg. Dette kaller vi for minnet til programmet:
- En samling av verdier (også kalt objekter eller data).
- En samling av variabler. Hver variabel har et navn og refererer til en verdi.
- En referanse til neste steg som skal utføres.
Dersom vi vet tilstanden til de tre komponentene over, er det en mekanisk prosess å beregne hvordan tilstanden vil endre seg når neste setning utføres.
La oss se på et eksempel:
|
|
Programmet over består av 6 setninger, som utføres linje for linje. Vi kan følge med på hvordan tilstanden til minnet endrer seg for hver setning som utføres; klikk på neste-knappen under for å se hvordan minnet endrer seg for hvert steg.
Vi kan se en tilsvarende sekvens for alle kodeeksempler på denne nettsiden ved å klikke på «se steg» -knappene under kodeeksemplene. Der vises «neste steg» som en rød pil. Prøv det gjerne med en gang!
En variabel endrer seg
En variabel er en navngitt referanse til en verdi. Når vi tilordner en ny verdi til en variabel, endrer vi hvilket objekt variabelnavnet refererer til. Den gamle verdien kan faktisk bli liggende i minnet en stund selv uten at noen peker på den – men etter en stund vil objekter som ingen refererer til bli automatisk slettet for å frigjøre plassen til noe annet.
Vi skal benytte følgende eksempel for å illustrere hva som skjer i minnet når en variabel endrer seg. Les gjennom koden og tenk igjennom hva hensikten med koden er; kjør koden og se hva den skriver ut. Deretter kan du klikke deg gjennom steg for steg hvordan minnet endrer seg.
Tips: forsøk å forutsi hva som skjer i minnet før du klikker deg videre til neste steg.
|
|
Legg spesielt merke til forskjellen på balance
-variabelen før og etter at linje 6 blir utført: det opprettes en ny verdi i minnet som balance-variabelen nå peker på. Den gamle verdien blir værende i minnet, og org_balance
-variabelen refererer fremdeles til den gamle verdien.
En variabel er en navngitt referanse; men hva er egentlig en referanse? I tegningene over tegner vi referanser som piler – men egentlig er det en tallverdi som angir på hvilken posisjon i listen over objekter verdien som det referes til ligger. Vi kan tenke på en referanse som en «adresse» til en posisjon i minnet hvor det befinner seg et objekt.
En funksjon blir kalt
Når en funksjon blir kalt, opprettes det en ny funksjonsramme i minnet. En funksjonsramme er egentlig bare en ny samling med variabler som legger seg «oppå» de gamle. Når funksjonen er ferdig, blir funksjonsrammen slettet fra minnet.
|
|
Debuggeren
I VSCode (og egentlig alle gode kodeeditorer for Python) finnes det en debug-modus som tillater oss å gå gjennom vår egen kode steg for steg på lignende måte som «se steg» -knappen gjør det for oss med kodeeksemplene på denne nettsiden.
En god gjennomgang laget av Boris Paskhaver:
Manuell kodesporing med tabell
Modellen av hva som skjer i minnet som er beskrevet i avsnittene over, gir oss en god forståelse av hva som egentlig skjer. Ulempen er at det er veldig mange tegninger å lage hvis du manuelt skal spore flere steg.
En kodesporingstabell er en forenklet modell av minnet som lar oss visualisere hva som skjer over flere steg på en mer kompakt måte. Denne metoden for å spore kode er som regel god nok i praksis; i hvert fall så lenge vi har den mer presise modellen i bakhodet.
Når vi sporer koden med en tabell, oppretter vi en tabell med én kolonne for hver variabel som finnes i den delen av koden vi skal spore. Den første kolonnen kan være en tidlinje som viser hvilket linjenummer i koden som skal utføres, og den siste kolonnen kan være utskrift. Deretter fyller vi ut rad for rad i tabellen nedover. Når vi kommer til et nytt steg i koden, fyller vi ut en ny rad i tabellen.
Samme eksempel som vi har sett tidligere:
|
|
linje | balance | org_balance | interest | difference | utskrift |
---|---|---|---|---|---|
1 | |||||
2 | 1000 | ||||
5 | |||||
6 | |||||
7 | Etter ett år har vi 1050.0 kroner på konto | ||||
10 | 52.5 | ||||
11 | 1102.5 | ||||
12 | Etter to år har vi 1102.5 kroner på konto | ||||
14 | 102.5 | ||||
15 | Vi har fått 102.5 kroner i renter etter to år |
- Vi hopper over blanke linjer i kildekoden. Vi kan også hoppe over linjer med utskrifter vi ikke er interessert i å spore.
- Det er ikke nødvendig å fylle ut alle cellene, bare de som endrer seg (vi vet jo at den reelle verdien til en ikke-utfylt celle vil være den nederste verdien som er fylt inn i samme kolonne).
- Hvis du har det travelt (og holder tungen rett i munnen) er det ikke nødvendig å inkludere kolonnen med linjenummer.
Manuell kodesporing av funksjoner med tabell
Et eksempel på kodesporing som inkluderer funksjonsskall:
|
|
linje | x | z | add a | add b | add total | add returverdi | utskrift |
---|---|---|---|---|---|---|---|
5 | 15 | ||||||
6; 1 | |||||||
6; 2 | |||||||
6; 3 | — | — | — | 22 | |||
6 | |||||||
7; 1 | |||||||
7; 2 | |||||||
7; 3 | — | — | — | 23 | |||
7 | 23 | ||||||
8 | 23 |
- Når kode inne i et funksjonskall kjøres, er vi på en måte flere steder i koden samtidig: både ved den konkrete kodelinjen inne i funksjonen som utføres, men også på linjen hvor funksjonen ble kalt. For eksempel er vi både på linje 6 og på linje 2 når total-variabelen blir satt til 22.
- Funksjonen add blir kalt to ganger, både på linje 6 og på linje 7. Variablene a, b, total og returverdiene for disse to ulike kallene til add-funksjonen er egentlig helt forskjellige variabler, som ikke hører hjemme i samme kolonne; men fordi vi vet at variablene i det første kallet fjernes før det andre kallet begynner, jukser vi litt og bruker samme kolonne likevel. Vi markerer at variabelen er slettet med å skrive en strek i tabellen der variablene opphører å eksistere.