アンティーク・アセンブラ~Antique Assembler

第5回 関数の機能 ~ 呼び出し元からの独立

この記事を読むのに必要な時間:およそ 4 分

局所的情報の保持

局所変数領域の確保

個々の関数実行時における固有の変数領域,いわゆる局所変数(local variable)は,関数における処理の開始から終了まで存在し続けます。

実のところ,継続(continuation)と呼ばれる概念をサポートするプログラミング言語では,関数からの復帰は,必ずしも局所変数領域の解放を意味しません。

継続サポートでの記述性向上による利便性と,資源消費のバランスについて考えてみるのもおもしろいでしょう。

局所変数領域は,再帰呼び出しのように自分自身が入れ子になる状況や,複数のスレッドが同じ関数を同時に呼びすような状況であっても,各呼び出しごとに独立して確保されなれければなりませんので,本連載のこれまでのプログラム例で見てきたような,あらかじめメモリ上に領域を確保する方法では実現できません。

このような特徴を持つ局所変数領域を実現するために,通常はスタック(stack)と呼ばれるデータ構造を用います。

スタック構造を用いて各関数の局所的な情報を格納する領域を「スタック領域」と呼び,通常はアドレス高位から低位へ向かってデータを格納します。このスタック領域を参照するためのレジスタをスタックポインタ(stack pointer)と呼び,32ビットIntel x86アーキテクチャの場合はESP(Extended Stack Pointer)レジスタが相当します。

「スタック領域」のデータ格納順がアドレス高位から低位に向かっているのは,慣れるまでは少々奇異に感じるかもしれません。

より詳しく知りたい場合は,実行時に割り当てられる,いわゆるヒープ(heap)領域との兼ね合いなど,OSレベルでのプロセス/メモリ管理について調べてみては如何でしょうか。

まずは関数での処理の開始の際に,以下のような処理を行います。

  1. ESPの値を4※3だけ減算
  2. EBP(Extended Base Pointer)の値をESPの位置に記録
  3. ESPをEBPの位置に移動
  4. 関数で必要となる局所変数領域の分だけESPを減算

図1 局所変数領域の確保

図1 局所変数領域の確保

この時,レジスタESPの指す位置(アドレス低位側)から,レジスタEBPの指す位置(アドレス高位側)までのスタック領域上のメモリが,その関数呼び出しにおける局所変数として使用できる領域となります。一般的にはこの領域をスタックフレーム(stack frame)と呼び,スタックポインタと対を成してスタックフレームを形成するためのレジスタを,フレームポインタ(frame pointer※4⁠)と呼びます。

スタックフレーム中に確保された局所変数領域へのデータの読み書きは,フレームポインタを使用したレジスタ相対での間接参照で行います。

たとえば,リスト3のCプログラムに相当するような処理は,リスト4のようにして実現されます。

リスト3 C言語での局所変数アクセス

void
func1(){
    int index;

    index = 1;
        :
}

リスト4 アセンブラでの局所変数アクセス

    # 局所変数領域の確保
    subl    $4, %esp     
                        # esp の移動
    movl    %ebp, (%esp) 
                        # ebp の格納
    movl    %esp, %ebp   
                        # ebp を esp 位置に移動
    subl    $4, %esp     
                        # index 領域の分だけ esp を移動

    # 局所変数へのアクセス
    movl    $1, -4(%ebp)

EBPと局所変数格納領域アドレスとの差分である"-4"を用いた即値付き間接アドレッシングで,当該メモリの内容変更=局所変数への代入を行います。

データ構造としてスタックを扱う場合,スタックポインタをデータ未格納方向に移動させつつデータを書き込む処理をプッシュ(push⁠⁠,その逆に,現位置からデータを読み出しつつスタックポインタを逆方向に移動させる処理をポップ(pop)と呼ぶのが通例です。

同名のアセンブラ命令(もちろん挙動も同一)をサポートするCPUアーキテクチャも少なくありません。

Intel x86アーキテクチャもサポートしており,スタックとのデータ入出力には push およびpopを使用するのが一般的です。

また,関数での処理の終了の際には,以下のような処理を行います。

  1. ESPの位置をEBPの位置に移動
  2. ESPの位置から以前のEBP値を取り出してEBPを復旧
  3. ESPの値を4だけ加算

図2 局所変数領域の解放

図2 局所変数領域の解放

アセンブラで実装するなら以下のようになります(この実装例では,⁠ebp の復旧」「esp の移動」pop 命令で実施しています⁠⁠。

リスト5 局所変数領域の解放

    movl    %ebp, %esp   # esp を ebp 位置に移動
    popl    %ebp         # ebp の復旧と,esp の移動

これにより,関数呼び出し元に復帰した際には,ESPおよびEBPは共に,呼び出し時点と変わらない状態に復旧していることになります。

※3)
4はEBPのデータサイズ(32bit⁠⁠。
※4)
Intel x86アーキテクチャでの名称は「ベースポインタ」ですが…、

著者プロフィール

藤原克則(ふじわらかつのり)

Mercurial三昧の日々が嵩じて, いつの間にやら『入門Mercurial Linux/Windows対応』を上梓。凝り性なのが災いして,年がら年中アップアップな一介の実装屋。最近は仕事の縁が元で,OpenSolarisに入れ込む毎日。