この回答で示唆されているように、これはハードウェアサポートの問題ですが、言語設計の伝統も役割を果たします。
関数が戻るとき、それは特定のレジスターに戻るオブジェクトへのポインターを残します
Fortran、Lisp、およびCOBOLの最初の3つの言語のうち、最初の言語では数学でモデル化された単一の戻り値が使用されました。2番目は、受信したのと同じ方法で任意の数のパラメーターをリストとして返しました(リストのアドレスである単一のパラメーターのみを渡して返したと主張することもできます)。3番目はゼロまたは1つの値を返します。
これらの最初の言語は、その後に続く言語の設計に大きな影響を与えましたが、複数の値を返すLispだけはあまり人気がありませんでした。
Cが登場したとき、Cはそれ以前の言語の影響を受けながら、ハードウェアリソースの効率的な使用に重点を置き、C言語が行ったこととそれを実装したマシンコードとの密接な関連を維持しました。「auto」変数と「register」変数などの最も古い機能の一部は、その設計哲学の結果です。
また、アセンブリ言語は80年代まで広く普及し、ついに主流の開発から段階的に廃止され始めたことも指摘する必要があります。コンパイラーを作成し、言語を作成した人々はアセンブリーに精通しており、ほとんどの場合、そこで最もうまく機能するものを維持していました。
この標準から逸脱した言語のほとんどは、あまり人気がなかったため、言語デザイナー(もちろん、彼らが知っていたことに触発された)の決定に影響を与える強力な役割を果たしたことはありません。
それでは、アセンブリ言語を調べてみましょう。最初に、Apple IIおよびVIC-20マイコンで有名な1975マイクロプロセッサである6502を見てみましょう。プログラミング言語のd明期の20、30年前の最初のコンピューターと比較すると強力でしたが、当時のメインフレームやミニコンピューターで使用されていたものと比較すると非常に弱かったです。
技術的な説明を見ると、5つのレジスタといくつかの1ビットフラグがあります。唯一の「フル」レジスタは、プログラムカウンタ(PC)でした。このレジスタは、実行される次の命令を指します。アキュムレータ(A)、2つの「インデックス」レジスタ(XおよびY)、およびスタックポインタ(SP)が存在するその他のレジスタ。
サブルーチンを呼び出すと、SPが指すメモリにPCが配置され、SPがデクリメントされます。サブルーチンから戻ることは逆に機能します。スタック上の他の値をプッシュおよびプルすることはできますが、SPに関連してメモリを参照することは困難であるため、再入可能なサブルーチンを記述することは困難でした。当然のことながら、サブルーチンの呼び出しはいつでも実行できると思いますが、このアーキテクチャではそれほど一般的ではありません。多くの場合、個別の「スタック」が作成され、パラメータとサブルーチンの戻りアドレスは別々に保たれます。
あなたは6502、インスピレーションを得たプロセッサを見れば6800を、それがSPから値を受け取ることができ、追加のレジスタ、インデックスレジスタ(IX)、など幅広いとしてSPを、持っていました。
マシン上で、リエントラントサブルーチンの呼び出しは、スタックにパラメーターをプッシュし、PCをプッシュし、PCを新しいアドレスに変更してから、サブルーチンがスタックにローカル変数をプッシュします。ローカル変数とパラメーターの数はわかっているため、それらのアドレス指定はスタックに関連して行うことができます。たとえば、2つのパラメーターを受け取り、2つのローカル変数を持つ関数は次のようになります。
SP + 8: param 2
SP + 6: param 1
SP + 4: return address
SP + 2: local 2
SP + 0: local 1
すべての一時スペースがスタック上にあるため、何度でも呼び出すことができます。
8080、TRS-80に使用し、CP / Mベースのマイコンのホストは、スタック上にSPをプッシュして、そのレジスタ間接、HLにそれをポップし、6800に似た何かをすることができます。
これは物事を実装する非常に一般的な方法であり、簡単に戻る前にすべてのローカル変数をダンプするベースポインターを使用して、より新しいプロセッサでさらにサポートされました。
問題は、どのように何かを返すのですか?プロセッサのレジスタは初期の段階ではそれほど多くありませんでした。多くの場合、アドレス指定するメモリを見つけるために、それらのいくつかを使用する必要がありました。スタック上にあるものを返すのは複雑です。すべてをポップし、PCを保存し、返されたパラメーターをプッシュし(一方、どこに保存されますか?)、PCを再度プッシュして戻る必要があります。
それで、通常行われたのは 、戻り値用に1つのレジスタを予約する。呼び出しコードは、戻り値が特定のレジスターにあることを知っていたため、保存または使用できるようになるまで保存する必要があります。
複数の戻り値を許可する言語を見てみましょう:Forth。Forthが行うことは、個別のリターンスタック(RP)とデータスタック(SP)を保持することです。そのため、関数はすべてのパラメーターをポップし、戻り値をスタックに残します。戻りスタックは独立しているため、邪魔になりませんでした。
コンピューターでの最初の6か月の間にアセンブリ言語とForthを学んだ人として、複数の戻り値は完全に正常に見えます。/mod
整数除算を返すForth'sなどの演算子と残りは明らかです。一方で、初期の経験がCの心であった人がその概念を奇妙に感じる方法を簡単に見ることができます。
数学については...まあ、私は数学のクラスの関数に到達する前にコンピューターをプログラミングしていました。数学の影響を受けるCSおよびプログラミング言語のセクション全体がありますが、再度、そうでないセクション全体があります。
したがって、数学が初期の言語設計に影響を及ぼし、ハードウェアの制約が容易に実装されるものを決定し、一般的な言語がハードウェアの進化に影響を与えた要因の合流点があります(LispマシンとForthマシンプロセッサは、このプロセスのロードキルでした)。