Subrutin
tanken på en subrutin utarbetades efter att datormaskiner redan hade funnits under en tid.De aritmetiska och villkorliga hoppinstruktionerna planerades i förväg och har förändrats relativt lite, men de speciella instruktionerna som används för procedursamtal har förändrats kraftigt genom åren.De tidigaste datorerna och mikroprocessorerna, som Manchester Baby och RCA 1802, hade inte en enda subrutinanropsinstruktion.Subrutiner kunde implementeras, men de krävde att programmerare skulle använda samtalssekvensen—en serie instruktioner—vid varje samtalsplats.
subrutiner implementerades i Konrad Zuses Z4 1945.1945 använde Alan M. Turing termerna ”bury” och ”unbury” som ett sätt att ringa och återvända från subrutiner.i januari 1947 presenterade John Mauchly allmänna anteckningar vid ’ett Symposium av storskaliga digitala beräkningsmaskiner’ under gemensam sponsring av Harvard University och Bureau of Ordnance, United States Navy. Här diskuterar han seriell och parallell operation som föreslår
…maskinens struktur behöver inte vara komplicerad en bit. Det är möjligt, eftersom alla logiska egenskaper som är väsentliga för denna procedur är tillgängliga, att utveckla en kodningsinstruktion för att placera subrutinerna i minnet på platser som är kända för maskinen och på ett sådant sätt att de lätt kan tas i bruk.
med andra ord kan man beteckna subrutin A som division och subrutin B som komplex multiplikation och subrutin C som utvärdering av ett standardfel i en sekvens av siffror och så vidare genom listan över subrutiner som behövs för ett visst problem. … Alla dessa subrutiner lagras sedan i maskinen, och allt man behöver göra är att göra en kort hänvisning till dem efter nummer, som de anges i kodningen.
Kay McNulty hade arbetat nära med John Mauchly på Eniac-teamet och utvecklat en IDE för subrutiner för ENIAC-datorn som hon programmerade under andra världskriget. hon och de andra Eniac-programmerarna använde subrutinerna för att beräkna missilbanor.Goldstine och von Neumann skrev en artikel daterad den 16 augusti 1948 om användningen av subrutiner.
några mycket tidiga datorer och mikroprocessorer, som IBM 1620, Intel 4004 och Intel 8008 och PIC-mikrokontroller, har ett subrutinsamtal med en enda instruktion som använder en dedikerad hårdvarustack för att lagra returadresser—sådan hårdvara stöder bara några få nivåer av subrutinnestning, men kan stödja rekursiva subrutiner. Maskiner före mitten av 1960—talet-som UNIVAC i, PDP—1 och IBM 1130-använder vanligtvis en anropskonvention som sparade instruktionsräknaren i den första minnesplatsen för den kallade subrutinen. Detta tillåter godtyckligt djupa nivåer av subrutinnestning men stöder inte rekursiva subrutiner. PDP-11 (1970) är en av de första datorerna med en stack-pushing subrutinanropsinstruktion; den här funktionen stöder både godtyckligt djup subrutinnestning och stöder också rekursiva subrutiner.
Språkstödredigera
i de mycket tidiga monterarna var subrutinstöd begränsat. Subrutiner separerades inte uttryckligen från varandra eller från huvudprogrammet, och faktiskt kunde källkoden för en subrutin blandas med den för andra underprogram. Vissa montörer skulle erbjuda fördefinierade makron för att generera samtal och retursekvenser. Vid 1960-talet hade montörer vanligtvis mycket mer sofistikerat stöd för både inline och separat monterade subrutiner som kunde kopplas samman.
subrutinbibliotekedit
även med detta besvärliga tillvägagångssätt visade sig subrutinerna vara mycket användbara. För en sak tillät de användningen av samma kod i många olika program. Dessutom var minnet en mycket knapp resurs på tidiga datorer, och subrutiner möjliggjorde betydande besparingar i storleken på programmen.
många tidiga datorer laddade programinstruktionerna i minnet från ett stansat pappersband. Varje subrutin kan sedan tillhandahållas av en separat bit tejp, laddad eller skarvad före eller efter huvudprogrammet (eller ”mainline”); och samma subrutinband kan sedan användas av många olika program. Ett liknande tillvägagångssätt tillämpas i datorer som använde stansade kort för deras huvudingång. Namnet subrutinbibliotek innebar ursprungligen ett bibliotek, i bokstavlig mening, som höll indexerade samlingar av band eller kortdäck för kollektivt bruk.
Return by indirect jumpEdit
för att ta bort behovet av självmodifierande kod tillhandahöll datordesigners så småningom en indirekt hoppinstruktion, vars operand istället för att vara returadressen själv var platsen för en variabel eller processorregister som innehåller returadressen.
på dessa datorer, istället för att ändra subrutinens returhopp, skulle det anropande programmet lagra returadressen i en variabel så att när subrutinen slutfördes skulle den utföra ett indirekt hopp som skulle rikta exekvering till den plats som ges av den fördefinierade variabeln.
Hoppa till subroutineEdit
ett annat framsteg var jump to subroutine-instruktionen, som kombinerade sparandet av returadressen med det anropande hoppet, vilket minimerar kostnaderna avsevärt.
i IBM-systemet / 360 skulle till exempel filialinstruktionerna BAL eller BALR, utformade för procedursamtal, spara returadressen i ett register som anges i instruktionen, enligt konventionsregister 14. För att återvända behövde subrutinen bara utföra en indirekt filialinstruktion (BR) genom det registret. Om subrutinen behövde det registret för något annat ändamål (som att ringa en annan subrutin), skulle det spara registrets innehåll till en privat minnesplats eller en registerstack.
i system som HP 2100 skulle JSB-instruktionen utföra en liknande uppgift, förutom att returadressen lagrades i minnesplatsen som var målet för filialen. Exekvering av proceduren skulle faktiskt börja vid nästa minnesplats. På HP 2100-monteringsspråket skulle man skriva, till exempel
... JSB MYSUB (Calls subroutine MYSUB.) BB ... (Will return here after MYSUB is done.)
för att ringa en subrutin som heter MYSUB från huvudprogrammet. Subrutinen skulle kodas 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 placerade adressen till nästa instruktion (nämligen BB) till den plats som anges som dess operand (nämligen MYSUB) och Grenades sedan till nästa plats efter det (nämligen AA = MYSUB + 1). Subrutinen kunde sedan återgå till huvudprogrammet genom att utföra det indirekta hoppet JMP MYSUB, I som grenade till platsen lagrad på plats MYSUB.
kompilatorer för Fortran och andra språk kan enkelt använda dessa instruktioner när de är tillgängliga. Detta tillvägagångssätt stödde flera nivåer av samtal; eftersom returadressen, parametrarna och returvärdena för en subrutin tilldelades fasta minnesplatser tillät det dock inte rekursiva samtal.
För övrigt användes en liknande metod av Lotus 1-2-3, i början av 1980-talet, för att upptäcka omräkningsberoenden i ett kalkylblad. Namnlösa: en plats reserverades i varje cell för att lagra returadressen. Eftersom cirkulära referenser inte är tillåtna för naturlig omräkningsordning tillåter detta en trädvandring utan att reservera utrymme för en stack i minnet, vilket var mycket begränsat på små datorer som IBM PC.
Call stackEdit
de flesta moderna implementeringar av ett subrutinsamtal använder en call stack, ett speciellt fall av stackdatastrukturen, för att implementera subrutinsamtal och returer. Varje proceduranrop skapar en ny post, kallad en stapelram, högst upp i stapeln; när proceduren återvänder raderas dess stapelram från stapeln och dess utrymme kan användas för andra proceduranrop. Varje stapelram innehåller privata data för motsvarande samtal, som vanligtvis innehåller procedurens parametrar och interna variabler och returadressen.
anropssekvensen kan implementeras med en sekvens av vanliga instruktioner (ett tillvägagångssätt som fortfarande används i RISC (reduced instruction set computing) och VLIW-arkitekturer (very long instruction word)), men många traditionella maskiner designade sedan slutet av 1960-talet har inkluderat speciella instruktioner för detta ändamål.
samtalsstacken implementeras vanligtvis som ett sammanhängande minnesområde. Det är ett godtyckligt designval om Stackens botten är den lägsta eller högsta adressen inom detta område, så att stacken kan växa framåt eller bakåt i minnet; men många arkitekturer valde den senare.
vissa mönster, särskilt några Forth implementeringar, använde två separata staplar, en huvudsakligen för kontrollinformation (som returadresser och loopräknare) och den andra för data. Den förra var eller fungerade som en samtalsstack och var endast indirekt tillgänglig för programmeraren genom andra språkkonstruktioner medan den senare var mer direkt tillgänglig.
När stackbaserade procedursamtal först introducerades var en viktig motivation att spara värdefullt minne. Med detta schema behöver kompilatorn inte reservera separat utrymme i minnet för privata data (parametrar, returadress och lokala variabler) för varje procedur. Stacken innehåller när som helst endast de privata uppgifterna för de samtal som för närvarande är aktiva (nämligen som har anropats men inte har återvänt ännu). På grund av hur program vanligtvis samlades från bibliotek var det (och är fortfarande) inte ovanligt att hitta program som innehåller tusentals subrutiner, varav endast en handfull är aktiva vid varje givet ögonblick. För sådana program kan samtalsstackmekanismen spara betydande mängder minne. Faktum är att samtalsstackmekanismen kan ses som den tidigaste och enklaste metoden för automatisk minneshantering.
en annan fördel med call stack-metoden är dock att den tillåter rekursiva subrutinsamtal, eftersom varje kapslat samtal till samma procedur får en separat instans av sina privata data.
fördröjd stapling redigera
en nackdel med anropsstackmekanismen är den ökade kostnaden för ett proceduranrop och dess matchande avkastning. Den extra kostnaden inkluderar inkrementering och minskning av stackpekaren (och i vissa arkitekturer, kontroll av stacköverflöde) och åtkomst till de lokala variablerna och parametrarna med ramrelaterade adresser istället för absoluta adresser. Kostnaden kan realiseras i ökad exekveringstid, eller ökad processorkomplexitet, eller båda.
denna overhead är mest uppenbar och stötande i bladprocedurer eller bladfunktioner, som återvänder utan att göra något förfarande kallar sig själva.För att minska den kostnaden försöker många moderna kompilatorer att fördröja användningen av en samtalsstack tills det verkligen behövs. Till exempel kan Anropet av ett förfarande P lagra returadressen och parametrarna för det anropade förfarandet i vissa processorregister och överföra kontrollen till procedurens kropp med ett enkelt hopp. Om proceduren p återgår utan att ringa något annat samtal används inte samtalsstacken alls. Om P behöver ringa ett annat förfarande Q, kommer det att använda samtalsstacken för att spara innehållet i alla register (t.ex. returadressen) som kommer att behövas efter Q returnerar.