Como sugerido nesta resposta , é uma questão de suporte de hardware, embora a tradição em design de linguagem também tenha um papel.
quando uma função retorna, deixa um ponteiro para o objeto retornado em um registro específico
Das três primeiras línguas, Fortran, Lisp e COBOL, a primeira utilizou um único valor de retorno conforme modelado em matemática. O segundo retornou um número arbitrário de parâmetros da mesma maneira que os recebeu: como uma lista (também se podia argumentar que apenas passava e retornava um único parâmetro: o endereço da lista). O terceiro retorna zero ou um valor.
Essas primeiras línguas influenciaram muito o design das línguas que as seguiram, embora a única que retornasse vários valores, Lisp, nunca tenha ganhado muita popularidade.
Quando o C chegou, apesar de influenciado pelas linguagens anteriores, ele enfatizou o uso eficiente de recursos de hardware, mantendo uma estreita associação entre o que a linguagem C fazia e o código da máquina que a implementava. Alguns de seus recursos mais antigos, como variáveis "auto" vs "register", são resultado dessa filosofia de design.
Deve-se também salientar que a linguagem assembly era amplamente popular até os anos 80, quando finalmente começou a ser eliminada do desenvolvimento convencional. As pessoas que escreviam compiladores e criavam idiomas estavam familiarizadas com o assembly e, na maioria das vezes, mantinham o que funcionava melhor lá.
A maioria das línguas que divergiu dessa norma nunca encontrou muita popularidade e, portanto, nunca teve um papel forte influenciando as decisões dos designers de linguagem (que, é claro, foram inspirados pelo que sabiam).
Então, vamos examinar a linguagem assembly. Vejamos primeiro o 6502 , um microprocessador de 1975 que foi famoso pelos microcomputadores Apple II e VIC-20. Era muito fraco em comparação com o que era usado no mainframe e nos minicomputadores da época, embora poderoso comparado aos primeiros computadores de 20, 30 anos antes, no início das linguagens de programação.
Se você olhar para a descrição técnica, ela possui 5 registros e alguns sinalizadores de um bit. O único registro "completo" foi o contador de programas (PC) - esse registro aponta para a próxima instrução a ser executada. Os outros registram onde o acumulador (A), dois "índices" registram (X e Y) e um ponteiro de pilha (SP).
Chamar uma sub-rotina coloca o PC na memória apontada pelo SP e, em seguida, diminui o SP. Retornar de uma sub-rotina funciona em sentido inverso. É possível empurrar e puxar outros valores na pilha, mas é difícil fazer referência à memória em relação ao SP, portanto, escrever sub-rotinas reentrantes foi difícil. Essa coisa que tomamos como certa, chamar uma sub-rotina a qualquer momento que acharmos conveniente, não era tão comum nessa arquitetura. Freqüentemente, uma "pilha" separada seria criada para que os parâmetros e o endereço de retorno da sub-rotina fossem mantidos separados.
Se você olhar para o processador que inspirou o 6502, o 6800 , ele tinha um registro adicional, o Index Register (IX), tão amplo quanto o SP, que poderia receber o valor do SP.
Na máquina, chamar uma sub-rotina reentrante consistia em empurrar os parâmetros na pilha, empurrar PC, mudar PC para o novo endereço e, em seguida, a sub-rotina empurrava suas variáveis locais na pilha . Como o número de variáveis e parâmetros locais é conhecido, é possível resolvê-los em relação à pilha. Por exemplo, uma função que recebe dois parâmetros e possui duas variáveis locais seria assim:
SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1
Pode ser chamado várias vezes porque todo o espaço temporário está na pilha.
O 8080 , usado no TRS-80 e em vários microcomputadores baseados em CP / M, poderia fazer algo semelhante ao 6800, pressionando SP na pilha e colocando-a em seu registro indireto, HL.
Essa é uma maneira muito comum de implementar as coisas e tem ainda mais suporte em processadores mais modernos, com o Ponteiro Base que facilita o despejo de todas as variáveis locais antes de retornar com facilidade.
O problema é como você retorna alguma coisa ? Os registros do processador não eram muito numerosos desde o início, e era necessário usar alguns deles até para descobrir qual pedaço de memória endereçar. Devolver as coisas na pilha seria complicado: você teria que estourar tudo, salvar o PC, pressionar os parâmetros de retorno (que seriam armazenados no local enquanto isso?), Depois pressionar o PC novamente e retornar.
Então, o que geralmente era feito era reservar um registro para o valor de retorno. O código de chamada sabia que o valor de retorno estaria em um registro específico, que teria que ser preservado até que pudesse ser salvo ou usado.
Vejamos uma linguagem que permite vários valores de retorno: adiante. O que a Forth faz é manter uma pilha de retorno separada (RP) e uma pilha de dados (SP), de modo que tudo que uma função precisava fazer era popular todos os seus parâmetros e deixar os valores de retorno na pilha. Como a pilha de retorno estava separada, ela não atrapalhou.
Como alguém que aprendeu a linguagem assembly e a Forth nos primeiros seis meses de experiência com computadores, vários valores de retorno parecem totalmente normais para mim. Operadores como o Forth's /mod
, que retornam a divisão inteira e o resto, parecem óbvios. Por outro lado, eu posso ver facilmente como alguém cuja experiência inicial era C acha esse conceito estranho: vai contra suas expectativas arraigadas sobre o que é uma "função".
Quanto à matemática ... bem, eu estava programando computadores muito antes de chegar a funções nas aulas de matemática. Não é uma seção inteira de CS e linguagens de programação que é influenciado pela matemática, mas, em seguida, novamente, há uma seção inteira que não é.
Portanto, temos uma confluência de fatores em que a matemática influenciou o design de linguagem inicial, onde as restrições de hardware ditavam o que era facilmente implementado e onde as linguagens populares influenciavam a evolução do hardware (a máquina Lisp e os processadores de máquina Forth foram atropelamentos nesse processo).