Подпрограма

от Уикипедия, свободната енциклопедия

Подпрограма е явление от програмирането, чрез което се описват поредица от програмни инструкции, които решават даден проблем – може да приема параметри и да връща стойност. Подпрограмата може да се приеме като строителен блок на програмата, т.к. в нея се извършва цялостната обработка на данни, която трябва да се извърши, за да се реши поставената задача. Подпрограмите могат да се дефинират в самата програма или в библиотеки, които могат да бъдат използвани от множество програми.

В различните програмни езици, за подпрограма се използват различни термини, като: процедура – Pascal, функцияPascal, C, C++, Java, JavaScript, Python, методC#, или подпрограмаVisual Basic, Perl. Функцията връща резултат, а процедурата – не.

Подпрограмата функционира като част от по-голяма програма или друга подпрограма. Една от причините за използването на подпрограми е избягването на повторението на кода, т.к. повтарящият се код затруднява поддръжката на програмата и води до грешки. Затова когато се използва даден фрагмент от програмния код повече от един път, е добра практика да бъде дефиниран като отделна подпрограма (метод), за да може да се изпълнява многократно. По този начин при изпълнението на дадена програма, подпрограмата може да бъде извикана няколко пъти от различни места в кода, включително и от други подпрограми.

Морис Уилкс, Дейвид Уилър и Стенли Джил измислят концепцията за подпрограмата, като първоначално я наричат „затворена подпрограма“,[1] което контрастира с т.нар. отворена подпрограма или макро.[2]

Подпрограмите са важна част от програмиране[3] и затова синтаксиса на много програмни езици включва поддръжка за писане и използване на подпрограми. Разумният подход за използване на подпрограми, напр. чрез структурно програмиране, намалява значително разходите за разработка и поддръжка на голяма програма като междувременно се повишава качеството и надеждността ѝ.[4] Подпрограмите, често записвани в библиотеки, са важен механизъм за споделяне и търгуване със софтуер. Дисциплината на обектно-ориентираното програмиране е базирана на обекти и методи, които са подпрограми свързани с тези класове или обекти.

Основни понятия[редактиране | редактиране на кода]

Съдържанието на подпрограмата е нейното „тяло“ – тази част от програмата, която се изпълнява когато подпрограмата е „извикана“.

Подпрограмата може да бъде написана така че да получи една или повече стойности от програмата, която я извиква параметри или условни параметри). Извикващата програма предоставя стойностите на тези параметри, наречени аргументи. Различните програмни езици могат да използват различни конвенции за предаването на аргументи:

Конвенция Описание Използва се в:
Извикване по стойност Аргументите се оценяват/изчисляват и „копие“ от изчислената стойност се предава на C, C++, Java
Извикване по референция Референция към аргумент C, Fortran, PL/I
Извикване по резултат Стойността на параметъра се копира обратно в аргумента след изпълнение на подпрограмата Ada OUT parameters
Извикване чрез стойност-резултат Стойността на параметъра се копира обратно в началото при влизане в подпрограмата и отново след изпълнението и Algol
Извикване по име Като макро – заменя параметрите с неизчислени аргументи Algol
извикване по константна величина Като извикване по стойност с изключение на това, че тук параметърът се третира като константа PL/I NONASSIGNABLE parameters, Ada IN parameters

Подпрограмата може да върне изчислена стойност на програмата или подпрограмата, която я е извикала или да предостави различни стойности или изходящи параметри. Всъщност, най-честата употреба на подпрограмата е имплементиране на математически функции, в които целта на подпрограмата е само да се изчисли една или повече стойности на резултатите, които са изцяло определени от параметрите, подадени на подпрограмата. (Примерите могат да включват изчисляване на логаритъм на число или детерминанта на матрица.)

Подпрограмата може да има и т.нар. странични ефекти, като модифициране структурата на данните в паметта, четене или писане върху периферно устройство, създаване на файл, спиране на програмата или машината, дори забавяне изпълнението на програмата за даден час. Подпрограма със странични ефекти (допълнителни функционалности) може да връща различни резултати след всеки път, когато е извикана, дори и да е извикана с едни и същи аргументи. Пример за това е генераторът на случайни числа, който връща различна стойност всеки път, когато е повикан. Широко разпространената употреба на подпрограми с подобни ефекти е характерна за езиците с императивно програмиране imperative programming.

Една подпрограма може да бъде програмирана така, че да бъде извикана от едно или повече места. Този метод дава възможност за директно изпълнение на функции, дефинирани чрез математическа индукция и рекурсия „разделяй и владей“ алгоритъм.

Подпрограма, чиято цел е да изчисли булев израз (това означава да отговори с да/не на въпрос) предикат.

Поддръжка на езика[редактиране | редактиране на кода]

Езиците за програмиране от високо ниво обикновено включват специфични конструкции, за да:

  • разграничат тази част от тялото на програмата, което представлява подпрограмата
  • зададе идентификатор (име) на подпрограмата
  • специфицират имената и/или типа на данните на параметрите и/или върнатите стойности
  • осигури отделен обхват за локалните променливи
  • идентифицират променливи извън подпрограмата, които са достъпни в нея
  • извикат подпрограма
  • присвоят стойности на параметрите
  • уточни връщаните стойности от тялото на подпрограмата
  • връщане към извикващата програма
  • разпореждане с върнатите от подпрограмата стойности
  • обработват всякакви изключения, които могат да бъдат срещнати докато се извика и изпълни дадена подпрограма
  • обединяват подпрограми в модул, библиотека, обект, клас и прочее.

В функционалните програмни езици като Haskell, подпрограмите могат да нямат (допълнителни функционалности) странични ефекти и винаги да връщат един и същ резултат ако са извикани многократно с едни и същи аргументи. Такива езици обикновено поддържат само функции, т.к. подпрограмите не връщат стойност и няма смисъл да бъдат използвани освен ако не се използват за страничен ефект (допълнителна функционалност).

В програмни езицикато C, C++, и C#, подпрограмите могат да бъдат наречени функции, но да не бъдат бъркани с математически функции.

Предимства[редактиране | редактиране на кода]

Предимствата на разделяне на една програма на подпрограми включва:

  • Разделяне на сложна задача на няколко по-прости стъпки: това е един от основните инструменти на структурното програмиране, заедно със структурите от данни
  • намаляване на дублиращия се код в програмата
  • позволява преизползване на кода в различни програми
  • разделяне на голяма задача на няколко програмиста на различен етап от разработката на софтуера
  • скриване на детайли относно имплементацията от потребителите на подпрограмата
  • подобряване на възможностите за търсене traceability, например в повечето езици има начини за получаване на информация за поредица от извиквания, която включва имената на включените в пътеката подпрограми и може би дори и допълнителни данни, като имена на файлове и номера на редове; ако кодът не се разделя на подпрограми, дебъгването би било сериозно затруднено

Недостатъци[редактиране | редактиране на кода]

Извикването на подпрограма (сравнено с използването на обикновен код) налага computational overhead в механизма на извикванията.

Подпрограмата обикновено изисква стандартен housekeeping код – и при входа и изхода

История[редактиране | редактиране на кода]

Поддръжка на езика[редактиране | редактиране на кода]

В ранните асемблери поддържането на подпрограми е било доста ограничено. Подпрограмите не са били отделени една от друга или от главната програма и кодът на подпрограмата може да се премеси с този на другите подпрограми. Някои асемблери могат да предложат предефинирани макроси за генериране на извикването на подпрограмата и връщането на резултат. По-късните асемблери (1960) имат много по-усъвършенствана поддръжка на подпрограми.

Самомодифициращ се код[редактиране | редактиране на кода]

Първата употреба на подпрограми е била на ранните компютри, програмирани с машинен код или асемблерни езици и са нямали точно определени инструкции за извикване. На тези компютри, всяко извикване на подпрограма е трябвало да бъде имплементирано като поредица от машинни инструкции на ниско ниво базирани на self-modifying code. Чрез замяната на операнд на branch instruction в края на тялото на процедурата, изпълнението на подпрограмата може да се върне на правилната локация (посочено от върнатия адрес) е извикващата програма (обикновено точно след инструкцията, която се прехвърля в подпрограмата.

Подпрограмни библиотеки[редактиране | редактиране на кода]

Дори и с този тромав подход, подпрограмите са много полезни. Те позволяват един и същи код да бъде използван в много и различни програми. Освен това, паметта е била много ограничен ресурс при първите компютри и чрез използването на подпрограми се позволяват значителни икономии в това отношение.

В много от първите компютри, програмните инструкции са били въведени в паметта от перфолента. В този случай всяка подпрограма може да бъде предоставена от отделна лента, заредена и обединена преди или след главната програма; същата подпрограмата може да бъде използвана от много различни програми. Подобен подход е бил използван и от компютри, при които се е работело с перфокарти перфокарта. Името подпрограмни библиотеки първоначално е означавало библиотека, в директен смисъл, която е съдържала индексирани групи от подобни ленти или карти за употреба от повече от един човек.

Връщане чрез индиректно прехвърляне[редактиране | редактиране на кода]

За да се премахне нуждата от самомодифициращия се код, разработчиците са включили indirect jump инструкция, чиито операнд, вместо самия той да бъде адреса за връщане, е съхранявал местоположението на променлива или регистър съдържащ в себе си адресът за връщане.

На тези компютри вместо да се модифицира връщането на контрола от подпрограмата, извикващата програма съхранява адресът за връщане в променлива, така че когато завърши изпълнението на подпрограмата, тя ще изпълни индиректно прехвърляне, което ще насочи изпълнението към позицията посочена от дефинираната вече променлива.

Преминаване към изпълнение на подпрограма[редактиране | редактиране на кода]

Друго подобрение е инструкцията за преминаване към изпълнение на подпрограмата, което комбинира записването на първоначалния адрес с calling jump, като по този начин минимизира overhead значително.

Например при IBM System/360, инструкции BAL или BALR създадени за извикване на подпрограма, биха запазили адреса за връщане в регистър на процесора упоменат в конкретната инструкция. За да се върне, подпрограмата трябва само да изпълни индиректна инструкция (BR) чрез този регистър. Ако този регистър е необходим на подпрограмата за друга цел (като например извикването на друга подпрограма), тя би записала съдържанието на регистъра в отделена част от паметта или стек регистър.

При HP 2100, инструкцията JSB би извършила подобни действия, само че адресът за връщане е бил съхраняван в чатта от паметта, която е била целта на инструкцията. Изпълнението на подпрограмата всъщност би започнала от следващата клетка на паметта. При HP 2100 assembly language, би трябвало да се запише например:

       ...
       JSB MYSUB (Calls subroutine MYSUB.) Извиква подпрограмата MYSUB
 BB ... (Ще се върне тук след като MYSUB приключи.)

за да извика подпрограма именована MYSUB от главната програма. Подпрограмата би била написана така:

 MYSUB NOP (Склад за адреса за връщане на MYSUB.)
 AA ... (Началото на тялото на MYSUB.)
       ...
       JMP MYSUB,I (Връща се към извикващата програма.)

Инструкцията JSB поставя адреса на инструкцията NEXT (namely, BB) на позицията посочена като неин операнд (namely, MYSUB), и след това свързана с NEXT позицията (namely, AA = MYSUB + 1). След това подпрограмата би могла да върне контрола на главната програма чрез изпълнението на индиректното прехвърляне JMP MYSUB,I which branched to the location stored at location MYSUB.

Компилаторите за Fortran и други езици биха могли лесно да се възползват от тези инструкции, когато са достъпни. Този подход поддържа множество нива на извиквания, обаче след като на адресът за връщане, параметрите и връщаните стойности бяха назначени фиксирани позиции в паметта, се получи така, че не бяха позволени рекурсивни извиквания.

Случайно подобен метод беше използван от Lotus 1-2-3 в началото на 1980 г., за да открие зависимости за преизчисляване в електронна таблица. Резервира се позиция във всяка клетка за да съхранява адресът за връщане. След като на circular reference не им е позволен естествен ред за преизчисляване, това позволява обхождане на дърво без да се резервира място за стек в паметта, която е била силно ограничена на малки компютри като IBM PC.

Стек на извикванията[редактиране | редактиране на кода]

Повечето модерни имплементации използват стек на извикванията, особен случай на структура от данни стек, за да имплементира извикванията на подпрограма и връщането на контрола към основната. Всяко извикване на подпрограма създава ново вписване, наречено stack frame, най-отгоре в стека. Когато контрола бъде върнат записът в стека се изтрива и мястото, което е заемал може да бъде използвано за други записи. Всеки запис в стека съдържа private data на отговарящото извикване, което обикновено включва параметрите на процедурата и вътрешни променливи, както и адресът за връщане.

Последователността от извиквания може да бъде имплементирана от поредица обикновени инструкции (подход все още използван в reduced instruction set computing (RISC) и very long instruction word (VLIW) архитектури), но много традиционни машини конструирани след края на 60-те години на 20 век са включили специални инструкции за тази цел.

Стека обикновено е имплементиран като съседни части от паметта. Въпрос на избор е дали дъното на стека е най-малкия или най-големия адрес в отделената част от паметта, така че стека може да нараства напред или назад в паметта.

Някои дизайни, особено някои Forth имплементации, са използвали два отделни стека, един главно за контролна информация като например адресите за връщане и броячите на цикли), а другият за данни. Първият е работил като стек за извиквания и е бил достъпен само индиректно за програмиста чрез други конструкции в програмния език, докато последния е бил директно достъпен.

Чрез извиквания на подпрограми, базирани на стека, компютърът не е необходимо да запазва отделно място в паметта ча частните данни (параметри, адрес за връщане, и локални променливи) на всяка подпрограма. Във всеки момент стекът съдържа частните данни на извикванията, които са активни (тези които са били извикани и не са върнали още контрола). Всъщност механизмът на стека може да се разглежда като най-ранния и най-простия метод за автоматично управление на паметта.

Друго предимство на метода с използване на стек е, че той позволява рекурсивни извиквания на подпрограма, след като всяко включено извикване на същата процедура получава отделна инстанция на частните си данни.

Delayed stacking [редактиране | редактиране на кода]

Една от отрицателните страни на механизмът със стек е увеличената цена на извикването на подпрограма и съответстващото връщане. Допълнителният разход включва увеличаване и намаляване на пойнтера на стека (и в някои архитектури, проверка за препълване на стека), и достъпването на локалните променливи и параметри чрез frame-relative адреси, вместо чрез абсолютни адреси. Този недостатък може да се отрази чрез по-дълго време за изпълнение на програмата, или увеличено натоварване на процесора, или и двете.

Посоченият недостатъък е най-лесно видим и неприятен при leaf procedures or leaf functions, които връщат контрола без да прявят извиквания на подпрограми.[5][6][7] Например извикването на процедура „P“ може да съхранява адресът за връщане и параметрите на извиканата процедура в определени процесорни регистри и да прехвърли контрола към тялото на процедурата чрез обикновен скок. Ако процедурата „P“ трябва да извика нова процедура „Q“, тогава ще използва кол стека за да запази съдържанието на регистрите (като например адресът за връщане), което ще бъде необходимо след като „Q“ върне контрола.

C и C++ примери[редактиране | редактиране на кода]

В езиците C and C++, подпрограмите са наименовани „функции“ (or member functions when associated with a class). Тези езици използват специален код void за да обозначат, че функцията не приема параметри (особено в С) и/или не връща стойност. C/C++ функциите могат да имат странични ефекти, включващи модификацията на променливи, чиито адреси са предадени като параметри (passed by reference). Примери:

 void function1(void) { /* some code */ }

Функцията не връща стойност и трябва да бъде извикана като самостоятелна, например function1();

 int function2(void)
 {
     return 5;
 }

Тази функция връща резултат (числото 5), като извикването и може да бъде част от израз e.g., x + function2()

 char function3(int number)
 {
     char selection[] = {'S','M','T','W','F','T','S'};
     return selection[number];
 }

Тази функция конвертира число между 0 и 6 към първата буква от съответния ден от седмицата, или 0 за ‘S’, 1 за ‘M’ и т.н. Резултатът от извикването и може да бъде .............. на променлива e.g., num_day = function3(number);.

 void function4(int *pointer_to_var)
 {
     (*pointer_to_var)++;
 }

Тази функция не връща стойност, а модифицира променливата, чиито адрес е предаден като параметър; би била извикана с „function4(&variable_to_increment);“.

Примери на Visual Basic 6[редактиране | редактиране на кода]

При езика Visual Basic 6, подпрограмите са наречени функции или subs (или методи когато са асоциирани с даден клас). Visual Basic 6 използва различни terms наречени „types“ за да дефинира какво се предава като параметър. По подразбиране, unspecified variable се регистрира като variant type и може да бъде passed като ByRef (default) или ByVal. Също така, когато функция или „sub“ е декларирана и се назначава public, private, or friend designation, което определя дали може да бъде достъпвана извън модула и/или проекта, в който е била декларирана.

  • By value [ByVal] – начин за предаване на стойност на аргумент към процедура, вместо предаване на адреса. Това позволява процедурата да достъпва копие на променливата. Резултатът е, че реалната стойност на променливата не може да бъде променена от процедурата към която е предадена.
  • By reference [ByRef] – начин за предаване на адрес на аргумент към процедура, вместо да се подава стойността. Това позволява на функцията да достъпва самата променлива, което означава, че реалната стойност може да бъде променяна от функцията на която е подадена. Ако не е посочено друго, аргументите се подават по референция.
  • Public (optional) – показва че функцията е достъпна за всички останали във всички модули. Ако бъде използвана в модул Private, функцията няма да е достъпна извън този проект.
  • Private (optional) – функцията е достъпна само за модула, в който е декларирана
  • Friend (optional) – използва се само в модул на клас. Функцията е видима в текущия проект, но не е видима за controller of an instance of an object.
Private Function Function1()
    ' Some Code Here
End Function

Функцията не връща стойност и трябва да бъде извикана самостоятелно e.g., Function1

Private Function Function2() as Integer
    Function2 = 5
End Function

Тази функция връща резултат (числото 5) и извикването и може да бъде част от израз, e.g., x + Function2()

Private Function Function3(ByVal intValue as Integer) as String
    Dim strArray(6) as String
    strArray = Array("M", "T", "W", "T", "F", "S", "S")
    Function3 = strArray(intValue)
End Function

Тази функция конвертира число между 0 и 6 към първата буква от съответния ден от седмицата, или 0 за ‘S’, 1 за ‘M’ и т.н. Резултатът от извикването и може да бъде .............. на променлива e.g., num_day = function3(number);.

Private Function Function4(ByRef intValue as Integer)
    intValue = intValue + 1
End Function

Тази функция не връща стойност, а модифицира промеливата, чиито адрес е предаден като параметър; би била извикана с „function4(&variable_to_increment);“.

P L/I пример[редактиране | редактиране на кода]

В PL/I, извиканата подпрограма подава описание на данните, осигурявайки информация за аргументите като дължина на низа или границите на масива. Това позволява подпрограмата да бъде по-обща и да елиминира нуждата програмистът да подава такава информация. Тривиална програма, която променя знака на всеки елемент от един двумерен масив изглежда така:

  change_sign: procedure(array);
    declare array(*,*) float;
    array = -array;
    end change_sign;

Това може да бъде извикано с различни, разнообразни по големина масиви, както се вижда:

/* границите на първия масив от -5 до 10 и от 3 до 9*/
  declare array1 (-5:10, 3:9)float;
/* границите на втория масив от 1 до 16 и от 1 до 16*/
  declare array2 (16,16) float;
  call change_sign(array1);
  call change_sign(array2);

Локални променливи, рекурсия и повторно започваща програма[редактиране | редактиране на кода]

За една подпрограма може да е полезно да има известно scratch място. Това е парче виртуална памет, което се използва по време на изпълнение на подпрограмата, за да съхранява временните резултати. Променливи, съхранени в това място се наричат локални, а мястото архив на активация или activation record. Архивът обикновено има връщащ адрес return address, който показва къде да се върне програмата след като подпрограмата приключи.

Една подпрограма може да извика определено подадено в нея число. Ако се поддържа рекурсия, подпрограмата може даже да извика себе си, пораждайки спирането на изпълнението, докато друго вложено изпълнение на същата подпрограма възниква. Рекурсията е полезно средство за опростяване на някои сложни алгоритми и премахване на цялостни проблеми. Рекурсивните езици по принцип осигуряват ново копие на локалните променливи при всяко повикване. Ако програмистът желае стойността на променливата да не се промени по време на повикванията, тя може да бъде декларирана като статична или static в някои програмни езици. Ето пример за рекурсия в С/С++ с цел намирането на числата на Фибоначи:

int fib(int n)
{
	if(n<=1) return n;
	return fib(n-1)+fib(n-2);
}

Ранните езици като Fortran не са поддържали рекурсия, защото променливите са били статично разпределени, както и връщащият адрес. Повечето компютри преди късните години на 1960, като PDP-8, не са поддържали хардуер стек регистри.

Модерните езици след ALGOL като PL/1 и С почти винаги използват стекове, за да предоставят нов архив на активация за всяко изпълнение на подпрограмата. По този начин вложеното изпълнение е свободно да променя своите локални променливи без притеснение за ефекта върху изпълненията в прогрес. Докато вложените повиквания се натрупват, се образува така наречената повикваща стек структура, състояща се от един архив на активация за всяка прекратена подпрограма. Всъщност стек структурата е на практика много често срещана и затова архивите на активация са често наричани стекови рамки или stack frames.

Някои програмни езици като Паскал и Ада също поддържат вложени функции или вложени подпрограми, които се извикват от външната подпрограма (подпрограмата родител). Вътрешните подпрограми имат достъп до локалните променливи, извиквани от външните подпрограми. Това е постигнато чрез съхраняването на допълнителна информация в архива на активация също наричан display.

Ако една подпрограма може да функционира коректно макар и извикана, когато друго изпълнение е вече в прогрес, то тя се нарича повторно започваща. А рекурсивната подпрограма трябва да е повторно започваща. Повторно започващите подпрограми са полезни в многонишковите ситуации, където много на брой нишки могат да извикат една и съща подпрограма, без да си пречат една на друга. В IBM, CICS (системи за обработка на транзакции), quasi-reentrant е по-малко ограничаваща, но подобна, препоръчвана за приложни програми, които споделят много нишки.

Претоварване[редактиране | редактиране на кода]

В строгите програмни езици понякога е желателно да има определени функции с едно и също име, чиито параметри обаче са от различен тип с различни стойности. Примерно, функция, която пресмята корен квадратен може да работи с реални числа, комплексни числа или матрици. Алгоритъмът, който ще се използва във всеки един от случаите е различен, както и изходният резултат. Създавайки три отделни функции с едно и също име, програмистът не е необходимо да помни различни имена за всеки различен тип данни. По-късно всеки тип може да бъде разделен на подтипове. Примерно, функцията, която пресмята с реални числа може да се раздели на две функции, едната връщаща числото, ако е положително, а другата – ако е отрицателно.

В обектно-ориентираното програмиране, когато в поредица от функции с еднакво име могат да бъдат приемани параметри от различен тип, може да се каже, че всяка от функциите се претоварва или методът се претоварва.

Ето пример за претоварване на подпрограма в C++:

#include<iostream>
using namespace std;
double area(double h,double w){
		return h*w;
}
double area(double r){
		return r*r*3.14;
}
int main(){
	double rectangle_area=area(3,4);
	double circle_area=area(5);
	cout<<rectangle_area<<endl;
	cout<<circle_area<<endl;
	return 0;
}

В този код има две функции с едно и също име, но с различни параметри.

Като друг пример, подпрограмата може да създаде обект, който да приема насоки и да следи пътя си по екрана. Тогава има голямо количество параметри, които трябва да бъдат предадени на конструктора като цвят на трасето, координатите х и у, по които се движи обектът, скоростта. Ако програмиста иска конструктор, който да може да приема само параметъра, съдържащ цвета, извиквайки го параметърът за цвета ще получи съответната си стойност, докато останалите ще получат стойностите си по подразбиране, които в повечето случаи са 0 или null, в зависимост от типа данни.

Прекратяване[редактиране | редактиране на кода]

Прекратяващата подпрограма, съдържа в себе си стойностите на някои променливи хванати от средата, в която са били създадени. Прекратяването е забележителна черта на езика за програмиране Lisp, въведен от Джон Маккарти. В зависимост от изпълнението, прекратяването служи като механизъм за нежелани реакции.

Конвенции[редактиране | редактиране на кода]

Оформили са се голям брой неписани споразумения за създаването на подпрограма. Колкото до техните имена е прието, ако подпрограмата извършва конкретна задача, да бъде наименована с глагол, ако прави някакво запитване – с прилагателно и съответно със съществително, когато се използва за заместване на променливи.

Някои програмисти смятат, че една подпрограма трябва да изпълнява само една задача и ако тя изпълнява повече, трябва да бъде разделяна на още подпрограми. Подпрограмите са ключов компонент в създаването и поддръжката на код и софтуер и се смята, че тяхната роля в програмите трябва да остане различна.

Привържениците на модулното програмиране смятат, че всяка подпрограма трябва да има минимална зависимост от другите парчета код. Примерно, при използването на глобални променливи се създава здраво свързване между подпрограмата и тези глобални променливи. Ако такова свързване не е необходимо, техния съвет е да се редактира програмата, така че параметъра да се подава. Въпреки това, увеличаването на броя на параметрите, подадени на подпрограмите може да повлияе на разбираемостта на кода.

Връщане на код[редактиране | редактиране на кода]

Понякога се налага подпрограмата да информира извиканата програма за извънредни условия, които могат да възникнат по време на изпълнението и. В някои програмни езици или стандарти, това се случва посредством връщащ код – целочислена стойност поставена от подпрограмата на някое стандартно място, където се кодират нормалните и извънредните условия.

В IBM System/360, където връщането на код се очаква от подпрограмата, върнатата стойност е често създадена, така че да е кратна на 4, за да може да бъде използвана като директен индекс в клон таблицата (branch table). Клон таблицата е вид ефикасен метод за прехвърляне на контрола върху програмата на друга част от програмата (или на различна програма, пуснато динамично), използвайки таблица от клони с инструкции. Клон таблицата често се намира веднага след повиканата инструкция, за да се избегнат допълнителни условни тестове и с цел по-нататъшно подобряване на ефективността. Ето пример в System/360:

         BAL 14,SUBRTN01 go to subroutine, using reg 14 as save register
                             (sets reg 15 to 0,4,8 as return value)
         B TABLE(15) use returned value in reg 15 to index the branch table,
                             branching to the appropriate branch instr.
TABLE B OK return code =00 GOOD }
         B BAD return code =04 Invalid input } Branch table
         B ERROR return code =08 Unexpected condition }

Оптимизация на извикването на подпрограма[редактиране | редактиране на кода]

Има значително увеличаване на времето за изпълнение на програмата, при извикването на подпрограма, което включва подаването на аргументите, разклоняването до подпрограмата и разклоняване обратно до извикването. Допълнителното време също включва в себе си и съхраняване и възстановяване на определени процесорни регистри, разпределяне и издирване на повикването в паметта и др. В някои програмни езици, всяко извикване на подпрограмата съдържа автоматично тестване на кода на връщане на подпрограмата или обработката на изключения, които може да възникнат. В обектно-ориентираните езици, значителен източник на допълнителното време е интензивно използваното динамично изпращане при извикването на методи.

Има някои привидно очевидни оптимизации на процедурата на повиквания, които не могат да се приложат, тъй като процедурите може да имат нежелани реакции. Така например, в израза (f(x)-1)/(f(x)+1), функцията if трябва да бъде извикана два пъти, защото двете извиквания може да върнат различни резултати. Освен това, стойността на x трябва да бъде извлечена наново преди второто повикване, понеже първото повикване може да я е променило. Определянето дали определена програма може да има нежелана реакция е много трудно. Следователно, докато тези оптимизации са безопасни в чисто функционални езици за програмиране, компилаторите на типичното наложително програмиране обикновено трябва да приемат най-лошото.

Вграждане[редактиране | редактиране на кода]

Метод, използван за да елиминира допълнителното време при изпълнението на програма, е вграждането на тялото на подпрограмата във всяко място на повикване (срещу разклоняването до подпрограмата и обратно). Това не само намалява времето за изпълнение, но и позволява на компилатора да оптимизира тялото на процедурата по-ефективно, като се вземат предвид контекста и аргументите в повикването. Вграждането обаче обикновено ще увеличи размера код, освен ако програмата не съдържа само едно повикване към подпрограмата или тялото на подпрограмата е по-малко като код от повикването.

Бележки[редактиране | редактиране на кода]

  1. Wilkes, M. V. и др. Preparation of Programs for an Electronic Digital Computer. Addison-Wesley, 1951. DOI:10.1145/609784.609816.
  2. Dainith, John. "open subroutine." A Dictionary of Computing. 2004. // Encyclopedia.com. Посетен на 14 януари 2013.
  3. Donald E. Knuth. The Art of Computer Programming, Volume I: Fundamental Algorithms. Addison-Wesley. ISBN 0-201-89683-4.
  4. O.-J. Dahl и др. Structured Programming. Academic Press, 1972. ISBN 0-12-200550-3.
  5. infocenter.arm.com
  6. msdn.microsoft.com
  7. msdn.microsoft.com