このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

呼び出しと戻り(callとreturn)を理解しよう

不定期、プログラミングのミニ知識シリーズ。(「シャローコピーとディープコピー」では、「そんなシリーズねーよ」って言いましたが、有りにします。)

ソフトウェアの歴史のなかで、最大の発明は何でしょう(ソフトウェアの概念=プログラム内蔵方式それ自身の発明は別として)。僕は、サブルーチン概念が一番の発明だと思ってます。(「サブルーチン」てのは、関数/メソッド/手続きなどをひとまとめにしてそう呼んでいます。)

そこで、サブルーチンの呼び出しと戻りのメカニズムを具体例で説明しましょう。領域の配置やアドレスなどは単純化して説明します。

前提と例題

ここでは、仮想的なハードウェア上で、C言語風の関数呼び出しを例題にします。これが典型的な事例だと思うからです。ハードウェアに備わったスタックを次の図で描きます(詳細は後で述べます)。


→→アドレスが高くなる(番号が増える)方向→
アドレス0番

□□□□…………………………………□□×××
                  ↑
           スタックはここまで
        ×印のところはスタックに使わない
←アドレスが低くなる方向←←
←スタックが伸びる方向←←

2つの整数aとbのそれぞれを平方(square)して足す(sum)関数をsqsumとします。

数学的定義は:
sqsum(a, b) = a2 + b2

戻り値は大きくなりがちですからlong整数とします。([追記 time="その日の夕方"]intもlongも差がないかもしれない、それだと意味ないけど、intが16ビット、longが32ビットの状況をイメージしてたりして、、、 下にあるもう1つの[追記]も参照。[/追記]


long sqsum(int a, int b);
この関数が主役です。

それと、今回の前提として;「『プログラマの常識』ってなによ? 」におけるid:bonotakeさんのコメント:

「引数にしろ戻り値にしろ、基本的にはスタック(フレーム)に場所は確保されるようになっていて、ただレジスタで受け渡しできる程度の大きさの時はレジスタ渡しで済ませる処理系が多い」というのが僕の認識です。

これをかんがみて、戻り値もスタックに置くことにします。

スタックとスタックポインタ

spというレジスタ(変数だと思ってもOK)にスタックトップのアドレス(番号の整数値)が入っています。spはstack pointerの略記です。注意すべきは、スタックがアドレス0番に向かって伸びることです。って、「伸びる」とかの言い回しが実はわかりにくい。


アドレス0番

□□□□…………□□■■……………■■×××
          ↑
          sp
         ×印のところはスタックに使わない
←スタックが伸びる方向←←

いま、spの値が100(百番、10進)だとすると、0から99番までの白い箱が未使用で、100番、101番、……の黒い箱はもう使用しているってことです。スタックを“伸ばす”とは、使用(むしろ使用予定だから予約)領域が増えることです。次はスタックを2バイト(箱2つ分)伸ばすコードです。


sp = sp - 2;

spの値を減らすことが伸ばすことだから注意してね、ってこと。ところで、なんでアドレスが減る方向に伸びるんでしょうね? 歴史的な事情だか、必然性があるんだか、僕は知りません。

フレームとその構造

sqsumのような1つの関数が作業中に使用するスタック領域を関数フレーム、または単にフレームと呼びます。スタックが使用されている(図では黒い箱)とは、そこに関数フレームが既に存在するってことです。

sqsumのフレームの構造はこんな感じ(括弧内はバイト数、fpや戻り番地は後で説明します):


        旧fpの待避領域(2)
ローカル変数領域↓↓  戻り値領域(4)
□□□□……□□□□■■■■■■■■■■
          ↑↑    ↑↑↑↑
          戻り番地(2) ||||
             引数領域(2+2)

[追記 time="その日の昼"]ウゲゲゲッ!! いま気がついたのだけど、なんと! intもアドレスも2バイト。しかも、書いていたときはまったく無意識に、、、ええそうですよ。わたしなんか16ビット時代の遺物なんですよ。64Kのヒトですよ、どうせ。

でもいいや、直さない。

領域の配置やアドレスなどは単純化して説明します。
仮想的なハードウェア上で、C言語風の関数呼び出しを例題にします。

つうことなんで、これは「単純化」なのです、「仮想的なハードウェア」は16ビットなんです。そうなんです。[/追記]

上の図では、sqsumを呼び出す側(親)が所有する領域と、sqsumがこれから使う予定の領域を一緒に描いています。黒い箱は親の側で、白い箱がsqsumが責任を持って管理すべき場所になります。もう少し正確にいうと、引数の値と戻り値領域は親がお膳立てして、戻り番地はハードウェア命令でセットされた状態で、sqsumの実行が開始されます。sqsumのフレームは図の白い部分です。

フレームの構造と、呼び出し側(親)と呼ばれ側(子)のお約束事により、関数呼び出しと戻り(親から子、子から親へと、仕事の連携と情報の受け渡し)が成立します。

続きがあるよ

長くなった(つうか、時間がなくなった)ので、呼び出し/戻りの手順は次回。