Datamaskiner forstår direkte kun instruksjoner i form av maskinkode. Dette er dermed det eneste programmeringsspråket som datamaskiner egentlig kan.
Selv om maskinkode er svært effektivt for maskinen, og nært knyttet til maskinens CPU som utfører den, er det så godt som uleselig for mennesker. Dels fordi koden kun består av binærsiffer, og dels fordi selv enkle oppgaver består av et stort antall detaljerte instruksjoner.
Et eksempel på maskinkode kan være som følger. Her er det formen, ikke selve innholdet i eksemplet som er interessant. Dataene er presentert i tabellform for å være oversiktlig, selv om de i realiteten vil være en fortløpende liste med bits:
Instruksjonsnummer |
Instruksjon |
Parameter 1 |
Parameter 2 |
Parameter 3 |
000001 |
000001 |
001101 |
000001 |
|
000010 |
000001 |
001111 |
000101 |
|
000011 |
000001 |
001000 |
000111 |
|
000100 |
000101 |
001101 |
001111 |
001111 |
000101 |
001001 |
001000 |
001111 |
000100 |
Dette kan oversettes til noe slik (selv om eksempelet ikke nødvendigvis er teknisk korrekt i forhold til instruksjoner og minneadresser)
- Sett minneadresse 13 til verdien 1
- Sett minneadresse 15 til verdien 5
- Sett minneadresse 8 til verdien 7
- Legg sammen verdiene på minneadresse 13 og 15, og legg svaret i minneadresse 15
- Hvis verdien på minneadresse 8 større enn verdien på minneadresse 15, hopp til instruksjon 4. Ellers, fortsett med en eventuelt neste instruksjon.
På grunn av kompleksiteten i å skrive og lese maskinkode, er det behov for et språk som er lettere for mennesker å skrive og forstå.
De første utgavene av et slikt programmeringspråk var ulike assemblerspråk. Her har instruksjonene i stedet fått lesbare navn, og man har innført navngitte variabler i stedet for minneadresser
Problemet er at det som er leselig for mennesket ikke kan leses direkte av maskinen. Dette løses ved hjelp av et spesielt program, kalt kompilator, som oversetter programkoden koden til binære sekvenser, altså maskinkode. Noen ganger gjøres denne oversettelsen én gang, og programmet er klart til fremtidig bruk. Andre ganger oversettes programmet direkte under kjøring hver gang det benyttes, gjennom en såkalt interpreter eller tolker.
Samme eksempel som vist for maskinkode, kan i et assemblerspråk se slik ut:
Instruksjonsnummer |
Instruksjon |
Parameter 1 |
Parameter 2 |
Parameter 3 |
1 |
SET |
A |
1 |
|
2 |
SET |
B |
5 |
|
3 |
SET |
C |
7 |
|
4 |
ADD |
A |
B |
B |
5 |
GT |
C |
B |
4 |
Mens maskinkode (binærkode) kun inneholder sekvenser av 0 og 1, skrives altså assemblerkode med lesbare instruksjoner (GT henviser til greater than) og navngitte variabler.
Med tiden utviklet også assemblerspråkene seg til å inkludere mer avanserte instruksjoner, som i praksis blir oversatt til en lang rekke maskinkodeinstruksjoner.
Utfordringen med disse tidlige språkene var at instruksjonen i stor grad var knyttet til hvilken type prosessor som maskinen hadde. Å lage programmer som kunne kjøre på ulike maskiner betydde da at man måtte skrive programkoden på nytt i flere ulike utgaver av assemblerspråk, men ulike kommandoer tilgjengelige.
Løsningen var å øke programmeringsspråkenes abstraksjonsnivå. Ved å utvikle språk som lå lenger unna maskinkoden og tilgjengelige instruksjoner ble det kompilatorens jobb å oversette instruksjonene til det riktige settet med instruksjoner for den aktuelle prosessortypen.
Ved å ikke lenger være bundet til maskinens instruksjonssett kunne man også begynne å utvikle programmeringsspråk som gjorde programmeringen enklere. Avanserte problemer kunne plutselig løses ved hjelp av noen få instruksjoner, som egentlig ikke finnes i prosessorens instruksjonsregister. Samtidig kunne ny syntaks gjøre programmeringen både mer effektiv, mer oversiktlig og lettlest samt mindre utsatt for feil. Kommandoer og instruksjoner er i større grad basert på ord og uttrykk fra menneskelige språk, spesielt engelsk, og matematiske uttrykksformer fremfor hvordan maskiner løser oppgaven på lavere nivå.
Eksempelet fra maskinkoden kan i et mer abstrahert språk se slik ut:
\(\begin{align} &\texttt{a = 1;} \\&\texttt{b = 5;} \\&\texttt{c = 7;} \\&\texttt{b = a + b;} \\&\texttt{if( c > b ) then goto 4;}\end{align}\)
Eller også slik om man beveger seg enda lenger opp på abstraksjonsnivå:
\(\begin{align} &\texttt{a = 1;} \\&\texttt{b = 5;} \\&\texttt{c = 7;} \\&\texttt{while( c > b )} \\&\texttt{\{} \\&\qquad\texttt{b = a + b;} \\&\texttt{\}}\end{align}\)
Et sted på veien i denne utviklingen skiller man mellom såkalte lavnivåspråk og høynivåspråk. Noen setter skillet mellom språkene som er bundet opp mot datamaskinens instruksjonssett og de som er frakoblet dette og i stedet har egne strukturer som variabler, funksjoner, kontrollstrukturer, operatorer og objekter. Typisk mellom assemblerspråkene og språk slik som C. Etterhvert som utviklingen av språk har gått videre har imidlertid mange flyttet grensen høyere opp i abstraksjonsnivået, uten at noen er enige omen standardisert definisjon.
Jo større abstraksjonsnivå et programmeringsspråk får, desto mer krevende blir jobben til kompilatoren som skal oversette programkoden til maskinkode som skal forstås av datamaskinen.
Flere elementer går igjen i de fleste høynivåspråk:
- ulike typer variabler, som tekst, tegn, heltall, reelle tall, logiske variabler (med verdiene sann og usann)
- operasjoner som utføres på disse variablene, blant annet aritmetiske operasjoner på tall og logiske operasjoner
- kommandoer relatert til inn- og utdataenheter
- adgangen til å gruppere rekker av kommandoer og operasjoner i enheter som funksjoner, prosedyrer og objekter, slik at man slipper å måtte gjenta dem hver gang de skal utføres
- adgangen til å strukturere operasjoner som skal gjentas et visst eller vekslende antall ganger
Kommentarer
Kommentarer til artikkelen blir synlig for alle. Ikke skriv inn sensitive opplysninger, for eksempel helseopplysninger. Fagansvarlig eller redaktør svarer når de kan. Det kan ta tid før du får svar.
Du må være logget inn for å kommentere.