Subrutine
ideen om en subrutine blev udarbejdet, efter at computermaskiner allerede havde eksisteret i nogen tid.De aritmetiske og betingede hoppeinstruktioner var planlagt på forhånd og har ændret sig relativt lidt, men de specielle instruktioner, der blev brugt til procedureopkald, har ændret sig meget gennem årene.De tidligste computere og mikroprocessorer, såsom Manchester Baby og RCA 1802, havde ikke en enkelt underrutine opkaldsinstruktion.Subrutiner kunne implementeres, men de krævede programmører at bruge opkaldssekvensen—en række instruktioner—på hvert opkaldssted.
subrutiner blev implementeret i Konrad S4 i 1945.i 1945 brugte Alan M. Turing udtrykkene “begrave” og “unbury” som et middel til at ringe og vende tilbage fra subrutiner.
i januar 1947 præsenterede John Mauchly generelle noter på ‘A Symposium of Large Scale Digital Calculating Machinery’ under det fælles sponsorat af Harvard University og Bureau of Ordnance, United States Navy. Her diskuterer han seriel og parallel operation, der antyder
…maskinens struktur behøver ikke at være kompliceret en smule. Det er muligt, da alle de logiske egenskaber, der er væsentlige for denne procedure, er tilgængelige, at udvikle en kodningsinstruktion til placering af underrutinerne i hukommelsen på steder, som maskinen kender, og på en sådan måde, at de let kan bruges.
med andre ord kan man betegne subrutine A som division og subrutine B som kompleks multiplikation og subrutine C som evaluering af en standardfejl i en sekvens af tal og så videre gennem listen over subrutiner, der er nødvendige for et bestemt problem. … Alle disse underrutiner gemmes derefter i maskinen, og alt hvad man skal gøre er at henvise til dem kort efter nummer, som de er angivet i kodningen.
Kay McNulty havde arbejdet tæt sammen med John Mauchly på ENIAC-teamet og udviklet en ide til subrutiner til ENIAC-computeren, hun programmerede under Anden Verdenskrig. hun og de andre ENIAC-programmører brugte subrutinerne til at hjælpe med at beregne missilbaner.
Goldstine og von Neumann skrev et papir dateret 16.August 1948, der diskuterede brugen af subrutiner.nogle meget tidlige computere og mikroprocessorer, såsom IBM 1620, Intel 4004 og Intel 8008, og PIC microcontrollere, har en enkelt instruktion subrutine opkald, der bruger en dedikeret Isenkræmmer stak til at gemme returadresser-sådan Isenkræmmer understøtter kun et par niveauer af subrutine nesting, men kan understøtte rekursive subrutiner. Maskiner før midten af 1960 ‘ erne-såsom UNIVAC i, PDP—1 og IBM 1130-bruger typisk en kaldekonvention, der gemte instruktionstælleren i den første hukommelsesplacering af den kaldte subrutine. Dette tillader vilkårligt dybe niveauer af subrutine nesting, men understøtter ikke rekursive subrutiner. PDP-11 (1970) er en af de første computere med en stak-skubbe subrutine opkald instruktion; denne funktion understøtter både vilkårligt dyb subrutine nesting og understøtter også rekursive subrutiner.
Sprogunderstøttetredit
i de meget tidlige montører var subrutine support begrænset. Subrutiner blev ikke eksplicit adskilt fra hinanden eller fra hovedprogrammet, og faktisk kunne kildekoden til en subrutine blandes med den for andre underprogrammer. Nogle montører ville tilbyde foruddefinerede makroer til at generere opkalds-og retursekvenserne. I 1960 ‘ erne havde montører normalt meget mere sofistikeret støtte til både inline og separat samlede subrutiner, der kunne knyttes sammen.
subrutine bibliotekeredit
selv med denne besværlige tilgang viste subrutiner sig meget nyttige. For det første tillod de brugen af den samme kode i mange forskellige programmer. Desuden var hukommelse en meget knap ressource på tidlige computere, og subrutiner tillod betydelige besparelser i størrelsen af programmer.
mange tidlige computere indlæste programinstruktionerne i hukommelsen fra et stanset papirbånd. Hver subrutine kunne derefter leveres af et separat stykke tape, indlæst eller splejset før eller efter hovedprogrammet (eller “hovedlinje”); og det samme subrutintape kunne derefter bruges af mange forskellige programmer. En lignende tilgang anvendt i computere, der brugte stansede kort til deres hovedindgang. Navnet subrutine bibliotek betød oprindeligt et bibliotek, i bogstavelig forstand, som holdt indekserede samlinger af bånd eller kortdæk til kollektiv brug.
retur af indirekte jumpEdit
for at fjerne behovet for selvmodificerende kode leverede computerdesignere til sidst en indirekte springinstruktion, hvis operand i stedet for at være selve returadressen var placeringen af en variabel eller processorregister indeholdende returadressen.
på disse computere, i stedet for at ændre underrutinens returhopp, ville opkaldsprogrammet gemme returadressen i en variabel, så når underrutinen blev afsluttet, ville den udføre et indirekte spring, der ville lede udførelsen til den placering, der er givet af den foruddefinerede variabel.
Spring til subrutineedit
et andet fremskridt var jump to subrutine-instruktionen, som kombinerede besparelsen af returadressen med det kaldende spring og derved minimerede overhead betydeligt.
i IBM-systemet / 360 vil for eksempel filialinstruktionerne BAL eller BALR, der er designet til procedureopkald, gemme returadressen i et processorregister, der er angivet i instruktionen, ved konventionsregister 14. For at vende tilbage måtte subrutinen kun udføre en indirekte greninstruktion (BR) gennem dette register. Hvis subrutinen havde brug for det register til et andet formål (såsom at kalde en anden subrutine), ville det gemme registerets indhold på en privat hukommelsesplacering eller en registerstak.
i systemer som HP 2100 ville JSB-instruktionen udføre en lignende opgave, bortset fra at returadressen blev gemt på den hukommelsesplacering, der var målet for filialen. Udførelse af proceduren ville faktisk begynde på den næste hukommelsesplacering. I HP 2100 samlingssprog ville man for eksempel skrive
... JSB MYSUB (Calls subroutine MYSUB.) BB ... (Will return here after MYSUB is done.)
for at kalde en subrutine kaldet MYSUB fra hovedprogrammet. Subrutinen ville blive kodet som
MYSUB NOP (Storage for MYSUB's return address.) AA ... (Start of MYSUB's body.) ... JMP MYSUB,I (Returns to the calling program.)
JSB-instruktionen placerede adressen til den næste instruktion (nemlig BB) på den placering, der er angivet som dens operand (nemlig MYSUB), og forgrenede sig derefter til den næste placering efter det (nemlig AA = MYSUB + 1). Subrutinen kunne derefter vende tilbage til hovedprogrammet ved at udføre det indirekte Spring JMP mysub, jeg som forgrenede sig til det sted, der er gemt på placering mysub.
kompilatorer til Fortran og andre sprog kan nemt gøre brug af disse instruktioner, når de er tilgængelige. Denne tilgang understøttede flere niveauer af opkald; men da returadresse, parametre og returværdier for en subrutine blev tildelt faste hukommelsesplaceringer, tillod det ikke rekursive opkald.
i øvrigt blev en lignende metode brugt af Lotus 1-2-3 i begyndelsen af 1980 ‘ erne til at opdage omberegningsafhængighederne i et regneark. Nemlig blev der reserveret en placering i hver celle for at gemme returadressen. Da cirkulære referencer ikke er tilladt for naturlig genberegningsrækkefølge, tillader dette en trævandring uden at reservere plads til en stak i hukommelsen, hvilket var meget begrænset på små computere som IBM PC.
Call stackEdit
de fleste moderne implementeringer af et subrutine-opkald bruger en opkaldsstak, et specielt tilfælde af stackdatastrukturen, til at implementere subrutine-opkald og retur. Hvert procedureopkald opretter en ny post, kaldet en stakramme, øverst i stakken; når proceduren vender tilbage, slettes dens stakramme fra stakken, og dens plads kan bruges til andre procedureopkald. Hver stakramme indeholder de private data for det tilsvarende opkald, som typisk inkluderer procedurens parametre og interne variabler og returadressen.opkaldssekvensen kan implementeres ved hjælp af en række almindelige instruktioner (en tilgang, der stadig bruges i reduceret instruktionssæt computing (RISC) og meget langt instruktionsord (VLI) arkitekturer), men mange traditionelle maskiner designet siden slutningen af 1960 ‘ erne har inkluderet specielle instruktioner til dette formål.
opkaldsstakken implementeres normalt som et sammenhængende hukommelsesområde. Det er et vilkårligt designvalg, om bunden af stakken er den laveste eller højeste adresse inden for dette område, så stakken kan vokse fremad eller bagud i hukommelsen; imidlertid valgte mange arkitekturer sidstnævnte.
nogle designs, især nogle Forth-implementeringer, brugte to separate stakke, den ene hovedsageligt til kontrolinformation (som returadresser og loop-tællere) og den anden til data. Førstnævnte var eller fungerede som en opkaldsstak og var kun indirekte tilgængelig for programmøren gennem andre sprogkonstruktioner, mens sidstnævnte var mere direkte tilgængelig.
da stakbaserede procedureopkald først blev introduceret, var en vigtig motivation at spare dyrebar hukommelse. Med denne ordning behøver kompilatoren ikke at reservere separat plads i hukommelsen til de private data (parametre, returadresse og lokale variabler) for hver procedure. På ethvert tidspunkt indeholder stakken kun de private data for de opkald, der i øjeblikket er aktive (nemlig som er blevet kaldt, men ikke er returneret endnu). På grund af de måder, hvorpå programmer normalt blev samlet fra biblioteker, var det (og er stadig) ikke ualmindeligt at finde programmer, der inkluderer tusinder af underrutiner, hvoraf kun en håndfuld er aktive på et givet tidspunkt. For sådanne programmer kan call stack-mekanismen spare betydelige mængder hukommelse. Faktisk kan call stack-mekanismen ses som den tidligste og enkleste metode til automatisk hukommelsesstyring.
en anden fordel ved call stack-metoden er imidlertid, at den tillader rekursive subrutine-opkald, da hvert indlejret opkald til den samme procedure får en separat forekomst af sine private data.
forsinket stabling Rediger
en ulempe ved call stack-mekanismen er de øgede omkostninger ved et procedureopkald og dets matchende afkast. De ekstra omkostninger inkluderer forøgelse og reduktion af stakmarkøren (og i nogle arkitekturer kontrol af stakoverløb) og adgang til de lokale variabler og parametre efter ramme-relative adresser i stedet for absolutte adresser. Omkostningerne kan realiseres i øget eksekveringstid eller øget processorkompleksitet eller begge dele.
denne overhead er mest indlysende og anstødelig i bladprocedurer eller bladfunktioner, som vender tilbage uden at foretage nogen procedure kalder sig selv.For at reducere denne overhead forsøger mange moderne kompilatorer at forsinke brugen af en opkaldsstak, indtil det virkelig er nødvendigt. For eksempel kan opkaldet til en procedure P gemme returadressen og parametrene for den kaldte procedure i visse processorregistre og overføre kontrol til procedurens krop ved et simpelt spring. Hvis proceduren p vender tilbage uden at foretage et andet opkald, bruges opkaldsstakken slet ikke. Hvis P har brug for at ringe til en anden procedure, bruger den derefter opkaldsstakken til at gemme indholdet i alle registre (f.eks.