UP | HOME

GCCにおける引数とスタックの関係 - 2023年度 システムプログラミング

GCCにおける引数とスタックの関係

問題

hanoi.s を例に spim-gcc の引数保存に関するスタックの利用方法について,説明せよ. そのことは,規約上許されるスタックフレームの最小値24 とどう関係しているか. このスタックフレームの最小値規約を守らないとどのような問題が生じるかについて解説せよ.

解答例

以下は, hanoi.s の冒頭部分の抜粋である.

1: _hanoi:
2:         subu    $sp,$sp,24
3:         sw      $ra,20($sp)
4:         sw      $fp,16($sp)
5:         move    $fp,$sp
6:         sw      $a0,24($fp)
7:         sw      $a1,28($fp)
8:         sw      $a2,32($fp)
9:         sw      $a3,36($fp)

hanoi 関数の冒頭はスタックを24バイトしか確保していないのにも関わらず, $a0〜$a3 は,それより後方, つまり,hanoi を呼出した関数が確保したスタック領域を使用している. この様子を下図に示す. offset = +00 以降の領域(ピンクで示した領域)は, 呼出し側が確保した領域である.(教科書とは天地が逆になっていることに注意)

$sp offset内容備考
新$sp→ -24 - 未使用
-20 - 未使用
-16 - 未使用
-12 - 未使用
-08 $fpフレームポインタ
-04 $ra戻りアドレス
旧$sp→ +00 $a0第1引数
+04 $a1第2引数
+08 $a2第3引数
+12 $a3第4引数
+16 ?? 呼出側で使用(あれば第5引数)
+20 ?? 呼出側で使用(あれば第6引数)
+24 ?? 呼出側で使用(あれば第7引数)
: ?? 呼出側で使用(あれば第8引数)

offset = +00 〜 +15 の領域を使用している部分は, 教科書には詳しく記述されていない. 教科書には,使用するスタックは,その関数内で確保することとしているが, 引数に関しては,そうではないことを明確にしていない(詳しく読めば分かる).

まとめると, 関数を呼出す側は $a0〜$a3 を保存する領域を余分に確保しておき, 呼出された側がその領域を使って引数を保存することになっている.

この決まりを守らない関数が,仮に呼出される側であった場合は, $a0〜$a3 の保存に自分で確保した領域しか使わないであろうから, 他の関数のスタック領域を破壊することがない. そのため,gcc から呼出しても問題がない. しかし,逆の場合は,問題がある. それは,自分の関数のために確保したスタックを呼出し先 (gccで記述してある)が破壊することになるからである.

gcc 同士で問題ならないのは, offset = -24 〜 -09 を見れば理解できる. gcc は,関数を呼出す際に,必ず 第1引数〜第4引数を保存するための領域を余分に確保して, 関数を呼出している. これによって,被呼出し関数がその場所に $a0〜$a3 を保存できるようになっている.

この方式には以下の利点がある.

  1. 被呼出し関数が $a0〜$a3 の保存をするか否かを決定できるので, 関数内で $a0〜$a3 を書換えなければ,この保存は省略できる. 従って,メモリへの書込み処理が減るため,高速化が望める. $a0〜$a3 を呼び出す側で保存することにしてしまうと, 4つの引数をメモリにストアする操作が必ず必要になる. これでは,せっかく引数をレジスタ渡しにしている利点がなくなってしまう.
  2. 第5引数以降が第4引数までの確保領域と連続するため, 被呼出し関数から見れば, 第1引数から第n引数までが規則正しくメモリ上に並ぶことになる. そのため,コンパイラの実装が容易になる.

C言語との連携には,この規約を守る必要があるため, 最小のスタックフレームサイズは,24バイトとなっている. (引数0〜3(a0〜a3), ra, fp の 6レジスタ x 4バイト = 24バイト)

Author: Yoshinari Nomura

Emacs 27.1 (Org mode 9.3)