Wie in dieser Antwort vorgeschlagen , handelt es sich um Hardwareunterstützung, wobei auch die Tradition im Sprachdesign eine Rolle spielt.
Wenn eine Funktion zurückgibt, hinterlässt sie einen Zeiger auf das zurückgegebene Objekt in einem bestimmten Register
Von den drei ersten Sprachen, Fortran, Lisp und COBOL, verwendete die erste einen einzelnen Rückgabewert, da dieser nach dem Vorbild der Mathematik erstellt wurde. Der zweite gab eine beliebige Anzahl von Parametern zurück, so wie er sie erhalten hat: als Liste (man könnte auch argumentieren, dass er nur einen einzigen Parameter übergeben und zurückgegeben hat: die Adresse der Liste). Der dritte Wert gibt null oder eins zurück.
Diese ersten Sprachen hatten großen Einfluss auf die Gestaltung der folgenden Sprachen, obwohl Lisp, die einzige, die mehrere Werte zurückgab, nie große Popularität erlangte.
Als C auf den Markt kam, konzentrierte es sich stark auf die effiziente Nutzung von Hardwareressourcen, wobei ein enger Zusammenhang zwischen der Funktionsweise der C-Sprache und dem Maschinencode, der sie implementiert hat, bestand. Einige der ältesten Funktionen, wie "auto" vs "register" -Variablen, sind das Ergebnis dieser Konstruktionsphilosophie.
Es muss auch darauf hingewiesen werden, dass die Assemblersprache bis in die 80er Jahre weit verbreitet war, als sie schließlich aus der allgemeinen Entwicklung ausstieg. Leute, die Compiler schrieben und Sprachen schufen, kannten sich mit Assembler aus und hielten sich größtenteils an das, was dort am besten funktionierte.
Die meisten von dieser Norm abweichenden Sprachen fanden nie große Beliebtheit und spielten daher keine große Rolle bei den Entscheidungen der Sprachgestalter (die sich natürlich von dem inspirieren ließen, was sie wussten).
Sehen wir uns also die Assemblersprache an. Schauen wir uns zunächst den 6502 an , einen 1975er Mikroprozessor, der berühmt für seine Apple II- und VIC-20-Mikrocomputer war. Es war sehr schwach im Vergleich zu dem, was damals in Großrechnern und Minicomputern verwendet wurde, obwohl es im Vergleich zu den ersten Computern vor 20, 30 Jahren zu Beginn der Programmiersprachen leistungsfähig war.
Wenn Sie sich die technische Beschreibung ansehen, hat sie 5 Register und einige Ein-Bit-Flags. Das einzige "volle" Register war der Programmzähler (PC) - dieses Register zeigt auf den nächsten auszuführenden Befehl. Die anderen Register enthalten den Akkumulator (A), zwei "Index" -Register (X und Y) und einen Stapelzeiger (SP).
Durch Aufrufen eines Unterprogramms wird der PC in den Speicher gestellt, auf den der SP zeigt, und der SP wird dann dekrementiert. Die Rückkehr von einem Unterprogramm erfolgt in umgekehrter Reihenfolge. Man kann andere Werte auf dem Stapel verschieben und abrufen, aber es ist schwierig, sich auf den Speicher relativ zum SP zu beziehen, so dass das Schreiben von wiedereintretenden Unterroutinen schwierig war. Diese Sache, die wir für selbstverständlich halten und die wir jederzeit als Unterprogramm aufrufen, war in dieser Architektur nicht so verbreitet. Oft wurde ein separater "Stapel" erstellt, so dass die Parameter und die Subroutinen-Rücksprungadresse getrennt gehalten wurden.
Wenn Sie sich den Prozessor ansehen, der den 6502 und den 6800 inspiriert hat , hat er ein zusätzliches Register, das Indexregister (IX), und das SP, das den Wert vom SP empfangen kann.
Beim Aufrufen einer wiedereintretenden Unterroutine auf dem Computer wurden die Parameter auf dem Stapel abgelegt, der PC abgelegt, der PC auf die neue Adresse geändert, und dann wurden die lokalen Variablen der Unterroutine auf dem Stapel abgelegt . Da die Anzahl der lokalen Variablen und Parameter bekannt ist, kann die Adressierung relativ zum Stapel erfolgen. Eine Funktion, die zwei Parameter empfängt und zwei lokale Variablen hat, würde beispielsweise so aussehen:
SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1
Es kann beliebig oft aufgerufen werden, da sich der gesamte temporäre Speicherplatz auf dem Stapel befindet.
Der 8080 , der auf TRS-80 und einer Vielzahl von CP / M-basierten Mikrocomputern verwendet wird, könnte etwas Ähnliches wie der 6800 tun, indem er SP auf den Stapel drückt und ihn dann in sein indirektes Register HL kopiert.
Dies ist eine sehr gebräuchliche Methode, um Dinge zu implementieren, und sie wurde auf moderneren Prozessoren noch mehr unterstützt, und zwar mit dem Basiszeiger, mit dem alle lokalen Variablen vor der Rückkehr einfach gesichert werden können.
Das Problem ist, wie Sie etwas zurückgeben ? Prozessorregister waren zu Beginn nicht sehr zahlreich, und man musste oft einige von ihnen verwenden, um herauszufinden, welcher Teil des Speichers adressiert werden sollte. Das Zurückgeben von Dingen auf den Stack wäre kompliziert: Sie müssten alles platzieren, den PC speichern, die Rückgabeparameter (die in der Zwischenzeit wo gespeichert würden?) Drücken, dann den PC erneut drücken und zurückkehren.
Normalerweise wurde also ein Register für den Rückgabewert reserviert . Der aufrufende Code wusste, dass sich der Rückgabewert in einem bestimmten Register befinden würde, das beibehalten werden musste, bis es gespeichert oder verwendet werden konnte.
Schauen wir uns eine Sprache an, die mehrere Rückgabewerte zulässt: Forth. Forth behält einen separaten Rückgabestapel (RP) und einen separaten Datenstapel (SP) bei, sodass eine Funktion nur alle ihre Parameter abrufen und die Rückgabewerte im Stapel belassen musste. Da der Rückgabestapel getrennt war, störte er nicht.
Als jemand, der Assembler und Forth in den ersten sechs Monaten Erfahrung mit Computern gelernt hat, sind mehrere Rückgabewerte für mich völlig normal. Operatoren wie Forths /mod
, die die Ganzzahldivision und den Rest zurückgeben, scheinen offensichtlich zu sein. Andererseits kann ich leicht erkennen, wie jemand, dessen erste Erfahrung C mind war, dieses Konzept seltsam findet: Es widerspricht seinen tief verwurzelten Erwartungen, was eine "Funktion" ist.
Was Mathematik angeht ... nun, ich habe Computer schon lange programmiert, bevor ich überhaupt in Mathematikunterricht kam. Es gibt eine ganze Sektion von CS und Programmiersprachen, die von Mathematik beeinflusst wird, aber es gibt auch eine ganze Sektion, die dies nicht ist.
Wir haben also einen Zusammenfluss von Faktoren, bei denen die Mathematik das frühe Sprachdesign beeinflusste, bei denen Hardwareeinschränkungen vorschrieben, was leicht zu implementieren war, und bei denen die populären Sprachen die Entwicklung der Hardware beeinflussten (die Maschinenprozessoren Lisp und Forth waren in diesem Prozess Roadkills).