Як запропоновано у цій відповіді , мова йде про технічну підтримку, хоча традиція в мовному дизайні також грає певну роль.
коли функція повертається, вона залишає вказівник на об'єкт, що повертається, у певному регістрі
З трьох перших мов, Fortran, Lisp та COBOL, перша використовувала єдине повернене значення, як воно моделювалося з математики. Другий повертав довільну кількість параметрів так само, як і отримував їх: як список (можна також стверджувати, що він лише передав і повертав один параметр: адресу списку). Третє поверне нуль або одне значення.
Ці перші мови сильно вплинули на дизайн мов, які слідували за ними, хоча єдиний, який повернув кілька значень, Лісп, ніколи не набирав великої популярності.
Коли C з'явився під впливом мов, що передували йому, він зосередив увагу на ефективному використанні апаратних ресурсів, підтримуючи тісну зв'язок між тим, що зробив мова С та машинним кодом, який його реалізував. Деякі з найдавніших його функцій, такі як змінні "auto" та "register", є результатом цієї філософії дизайну.
Слід також зазначити, що мова складання була широко популярною до 80-х років, коли її нарешті почали виводити з лайн розвитку. Люди, які писали компілятори та створювали мови, були знайомі зі складанням, і здебільшого дотримувались того, що там найкраще працювало.
Більшість мов, які відходили від цієї норми, ніколи не знаходили великої популярності, а отже, ніколи не відігравали сильної ролі, що впливали на рішення дизайнерів мови (які, звичайно, були натхнені тим, що вони знали).
Тож давайте розглянемо мову складання. Давайте спочатку розглянемо 6502 , мікропроцесор 1975 року, який знаменито використовували мікрокомп'ютери Apple II та VIC-20. Це було дуже слабко порівняно з тим, що використовувалося в мейнфреймі та мінікомп'ютерах того часу, хоча й потужним порівняно з першими комп’ютерами за 20, 30 років до цього, на зорі мов програмування.
Якщо ви подивитесь на технічний опис, у нього є 5 регістрів плюс кілька однобітних прапорів. Єдиним "повним" реєстром був лічильник програм (ПК) - той регістр вказує на наступну інструкцію, яку потрібно виконати. Інші регістри, де акумулятор (A), два "індексні" регістри (X і Y) і вказівник стека (SP).
Виклик підпрограми вводить ПК у пам'ять, на яку вказує SP, а потім зменшує SP. Повернення з підпрограми працює в зворотному порядку. На стеку можна натиснути та витягнути інші значення, але важко посилатися на пам'ять щодо SP, тому писати підпрограми для повторного вступу було важко. Ця річ, яку ми сприймаємо як належне, викликаючи підпрограму в будь-який час, коли нам здається, не була такою поширеною в цій архітектурі. Часто створюється окремий "стек", щоб параметри та повернення підпрограми залишалися окремими.
Якщо ви подивитеся на процесор, який надихнув 6502, 6800 , він мав додатковий регістр, індекс регістру (IX), ширину як SP, який міг отримати значення від SP.
На машині виклик підпрограми повторного учасника полягав у натисканні параметрів на стеку, натисканні ПК, зміні ПК на нову адресу, і тоді підпрограма підштовхує свої локальні змінні на стек . Оскільки кількість локальних змінних і параметрів відома, їх адресація може бути виконана відносно стека. Наприклад, функція, яка отримує два параметри і має дві локальні змінні, виглядатиме так:
SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1
Його можна назвати будь-яку кількість разів, оскільки весь тимчасовий простір знаходиться на стеці.
8080 , який використовується на ТРС-80 і безліч CP / M мікрокомп'ютерів на основі могли б зробити що - щось схоже на 6800, натиснувши SP на стек , а потім вискакують його на непрямому регістрі, HL.
Це дуже поширений спосіб реалізації речей, і він отримав ще більшу підтримку більш сучасних процесорів, з базовим покажчиком, який робить демпінг усіх локальних змінних перед поверненням простий.
Проблема полягає в тому, як повернути щось ? Реєстри процесорів були не дуже численні на ранніх етапах, і часто потрібно було використовувати деякі з них навіть для того, щоб дізнатися, до якого фрагмента пам'яті потрібно звернутися. Повернення речей у стеку було б складним: вам доведеться вискакувати все, зберегти ПК, натиснути параметри повернення (які зберігатимуться там, де тим часом?), Потім знову натиснути ПК та повернутися.
Тож зазвичай робилося резервування одного регістра для значення повернення. Код, що викликає, знав, що повернене значення буде в певному реєстрі, яке повинно бути збережене, поки воно не може бути збережене або використане.
Давайте розглянемо мову, яка дійсно дозволяє отримати кілька повернених значень: Forth. Що Форт робить, це зберігати окремий стек повернення (RP) та стек даних (SP), так що вся функція повинна була виконати поп всі його параметри і залишити значення повернення на стеку. Оскільки стек повернення був окремим, він не заважав.
Як хтось, хто навчився мовою складання та Forth за перші шість місяців досвіду роботи з комп’ютерами, багатозначні повернені значення мені здаються цілком нормальними. Такі оператори, як Forth /mod
, які повертають ціле поділ та інші, здаються очевидними. З іншого боку, я легко бачу, як хтось, чий ранній досвід був розумом С, вважає цю концепцію дивною: це суперечить їх вродженим очікуванням, що таке "функція".
Що стосується математики ... ну, я програмував комп'ютери так, перш ніж потрапити на функції на уроках математики. Там є цілий розділ CS і мов програмування , який під впливом математики, але, знову ж , є цілий розділ , який не є.
Таким чином, у нас є сукупність факторів, коли математика впливала на раннє мовне проектування, де обмеження в техніці диктували те, що легко реалізувати, і де популярні мови впливали на розвиток екіпірування обладнання (машинні процесори Lisp і машинні процесори Forth були справжніми труднощами в цьому процесі).