Comme suggéré dans cette réponse , il s’agit d’une prise en charge matérielle, bien que la conception traditionnelle du langage joue également un rôle.
quand une fonction retourne, elle laisse un pointeur sur l'objet retourné dans un registre spécifique
Parmi les trois premières langues, le Fortran, le Lisp et le COBOL, la première utilisait une seule valeur de retour car elle était modelée sur les mathématiques. La seconde a renvoyé un nombre arbitraire de paramètres de la même manière qu’elle les a reçus: sous forme de liste (on pourrait également affirmer qu’elle n’a fait que transmettre et renvoyer un seul paramètre: l’adresse de la liste). Le troisième retourne zéro ou une valeur.
Ces premières langues ont beaucoup influencé la conception des langues qui les ont suivies, bien que Lisp, la seule qui ait renvoyé de multiples valeurs, n’ait jamais été très appréciée.
Lorsque C est arrivé, bien que influencé par les langages précédents, il accordait une importance particulière à l’utilisation efficace des ressources matérielles, tout en maintenant une association étroite entre ce que faisait le langage C et le code machine qui l’implémentait. Certaines de ses caractéristiques les plus anciennes, telles que les variables "auto" ou "registre", sont le résultat de cette philosophie de conception.
Il faut également souligner que le langage d'assemblage était très populaire jusque dans les années 80, quand il a finalement commencé à disparaître du développement traditionnel. Les personnes qui écrivaient des compilateurs et créaient des langages étaient habitués à l’assemblage et, pour l’essentiel, restaient fidèles à ce qui fonctionnait le mieux là-bas.
La plupart des langues qui ont divergé par rapport à cette norme n’ont jamais rencontré une grande popularité et n’ont donc jamais joué un rôle important en influençant les décisions des concepteurs de langages (qui, bien sûr, s’inspiraient de ce qu’ils savaient).
Allons donc examiner le langage d'assemblage. Examinons tout d’abord le 6502 , un microprocesseur de 1975, qui était utilisé par les micro-ordinateurs Apple II et VIC-20. Il était très faible par rapport à ce qui était utilisé dans les ordinateurs centraux et les mini-ordinateurs de l’époque, bien que puissant par rapport aux premiers ordinateurs datant de 20 ou 30 ans plus tôt, à l’aube des langages de programmation.
Si vous regardez la description technique, il y a 5 registres plus quelques drapeaux à un bit. Le seul registre "complet" était le compteur de programme (PC) - ce registre pointe vers l'instruction suivante à exécuter. L’autre enregistre l’accumulateur (A), deux registres "index" (X et Y) et un pointeur de pile (SP).
L'appel d'un sous-programme place le PC dans la mémoire indiquée par le SP, puis décrémente le SP. Le retour d'un sous-programme fonctionne en sens inverse. On peut pousser et extraire d'autres valeurs sur la pile, mais il est difficile de faire référence à la mémoire par rapport au SP, il était donc difficile d' écrire des sous-routines réentrantes . Cette chose que nous prenons pour acquis, appeler un sous-programme à tout moment, n’est pas si commune sur cette architecture. Souvent, une "pile" distincte serait créée afin que les paramètres et l'adresse de retour du sous-programme soient conservés séparément.
Si vous regardez le processeur qui a inspiré le 6502, le 6800 , il possédait un registre supplémentaire, le registre d'index (IX), aussi large que le SP, qui pouvait recevoir la valeur du SP.
Sur la machine, appeler un sous-programme rentrant consistait à appliquer les paramètres à la pile, à PC, à changer d’ordinateur à la nouvelle adresse, puis le sous-programme poussait ses variables locales sur la pile . Le nombre de variables et de paramètres locaux étant connu, vous pouvez les adresser par rapport à la pile. Par exemple, une fonction recevant deux paramètres et ayant deux variables locales ressemblerait à ceci:
SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1
Il peut être appelé n'importe quel nombre de fois, car tout l'espace temporaire est sur la pile.
Le 8080 , utilisé sur le TRS-80 et une multitude de micro-ordinateurs basés sur CP / M, pourrait faire quelque chose de similaire au 6800, en plaçant SP sur la pile, puis en la plaçant sur son registre indirect, HL.
C’est un moyen très courant d’implémenter les choses, et il bénéficie d’un soutien encore plus grand pour les processeurs plus modernes, avec le pointeur de base qui permet de vider toutes les variables locales avant de revenir facilement.
Le problème, comment est- ce que vous retournez quelque chose ? Les registres de processeurs n’étaient pas très nombreux au début, et il fallait souvent en utiliser certains même pour savoir à quel morceau de mémoire s’adresser. Retourner des choses sur la pile serait compliqué: il faudrait tout sauter, sauvegarder le PC, pousser les paramètres de retour (qui seraient stockés entre-temps?), Puis repousser le PC et revenir.
Donc, ce qui était généralement fait était de réserver un registre pour la valeur de retour. Le code appelant savait que la valeur de retour se trouverait dans un registre particulier, qu'il faudrait conserver jusqu'à ce qu'il puisse être sauvegardé ou utilisé.
Regardons un langage qui autorise plusieurs valeurs de retour: Forth. Forth conserve une pile de retour (RP) et une pile de données (SP) distinctes, de sorte qu’une fonction n’a à faire que de supprimer tous ses paramètres et de laisser les valeurs de retour dans la pile. Étant donné que la pile de retour était séparée, cela ne gênait pas.
En tant que personne ayant appris le langage d'assemblage et Forth au cours des six premiers mois d'expérience en informatique, les valeurs de retour multiples me paraissent tout à fait normales. Des opérateurs tels que celui de Forth /mod
, qui renvoie la division entière et le reste, semblent évidents. D'autre part, je peux facilement voir comment une personne dont l'expérience initiale était l'esprit C trouve ce concept étrange: cela va à l'encontre de leurs attentes profondes de ce qu'est une "fonction".
Pour ce qui est des maths ... eh bien, je programmais bien les ordinateurs avant d’avoir des fonctions dans les cours de mathématiques. Il y a toute une section de CS et de langages de programmation qui sont influencés par les mathématiques, mais, encore une fois, il y a toute une section qui ne l'est pas.
Nous avons donc une confluence de facteurs dans lesquels les mathématiques ont influencé la conception linguistique précoce, où les contraintes matérielles dictaient ce qui était facilement implémenté, et où les langages populaires influençaient l'évolution du matériel (les processeurs Lisp et Forth étaient des destructeurs de processus dans ce processus).