読者です 読者をやめる 読者になる 読者になる

アセンブリにおけるC言語のstatic(静的)変数とauto(自動)変数の取り扱い方

とあるC言語のプログラムをアセンブリ言語に変換して、そこから、C言語のstaticとautoをアセンブリ言語ではどのように扱っているのか確認してみる。

C言語およびそれをmips-gccでクロスコンパイルしたプログラムは以下。

Programs in Assembly and C compiled by mips-gcc

すげーーー長くなったな笑

ということで.sの方を読み解いて確認してみる。

static

まずstaticについて。こっちは分かりやすい。

.sプログラムの最後に以下のような記述がある。

 .comm   _primes_stat,40

これは簡単に示すと以下とおんなじ意味。

 .data
    .align 2
    _primes_start:
        .space  40

データセグメント内に、_primes_startというラベルをつけて、データを40バイト確保している。

つまりこのときに長さ10の配列のために確保された40バイトは、この配列を使うためだけに確保されている。つまり プログラムの開始から終了時まで、値を保持しつづけるという性質を持つことになる。

だけどもちろん絶対上書きされないなんてことはない。関数などが再帰的に呼び出された場合は、その領域を上書きすることがある。

その領域しか使わないからこそ、再帰的な処理の時などの管理が大変そうだ。

auto

main部分にフォーカスして読んでいく。

まず64バイト分のスタックフレームを生成し、戻りアドレス$raとフレームポインタ$fpを更新。

main:
    subu    $sp,$sp,64
    sw  $ra,60($sp)
    sw  $fp,56($sp)

その後フレームポインタを$spと同じアドレスを指すようにする。

上で一度$fpを更新してもう一度ここで更新する意味がわからないけど。まぁコンパイラの仕様かな。忠実に手続き呼び出し規約には従いつつ、みたいな。

 move    $fp,$sp

そしたら各配列の先頭の値を準備する。

まずはstaticの方。

$v0の値を_primes_startラベルで確保されてるデータセグメントの最初にぶっこんでることに注意。

 li  $v0,2           # 0x2
    sw  $v0,_primes_stat

そしたらauto。16($fp)ということで、スタックフレームのデータ4個分のところに値を突っ込んでる。

なぜ16なのか。不明。

 li  $v0,3           # 0x3
    sw  $v0,16($fp)

出力する予定の文字列を読み込んで、さっき格納したstaticの配列の一番最初の値を持ってきて、出力するためのルーチンを呼ぶ。

 la  $a0,$LC3
    lw  $a1,_primes_stat
    jal _print_var

今度はautoでも同じことをする。

 la  $a0,$LC4
    lw  $a1,16($fp)
    jal _print_var

スタックを解放してmain部終了。

 move    $sp,$fp
    lw  $ra,60($sp)
    lw  $fp,56($sp)
    addu    $sp,$sp,64
    j   $ra

ここからわかるように、auto変数では、static変数と違って スタックで配列を管理しているということである。

つまり、この関数が終了すれば$spに確保したバイト数が足され、スタックが解放され、その結果保持していたauto変数の配列もボーンと消えてしまう。

アセンブリ言語ではC言語のstaticとautoを実現しているらしい。なるほど!!

疑問点

  • $fpを2回更新する理由
  • 16(fp)とフレームポインタ(=スタックポインタ)+ 16バイト目に突っ込んでいるのか。