トップページ

本棚


両手で1000まで数えられますか?:2進法の話
数値 - 危険物取り扱い注意
100 × 200 =32?:変数のオーバーフローの話
パリティビット
小数の扱いについて
ぽいんた
ぽいんた2
ぽいんた3
ジ・オリジン
ジ・オリジン:補足
かっこつける話
かっこつける話2
文字列のこと
タイミングの話
拡張性の話
取り込む話
staticな話

コンパイルの話1
コンパイルの話2:止まらぬビルド
コンパイルの話3:マシン語に落ちるということ1:メモリの話
コンパイルの話4:マシン語に落ちるということ2:最適化
コンパイルの話5:マシン語に落ちるということ3:変数とスタック
コンパイルの話6:コンパイラはそもそも何をやってくれるのか??
リンクの話
プリプロセッサの話

OS、というもの

オブジェクト指向1
オブジェクト指向:2
オブジェクト指向:3

オブジェクト指向:番外 C言語のソースファイルの話
オブジェクト指向:番外 C言語での「再利用性」と「カプセル化」データ構造とアルゴリズム


抽象的な話

寝込んで布団の中で考えたこと
こんなの、常識??
お仕事プログラミング
ソフトでハードなプログラム
プログラムするということ
お勉強
プログラムを学ぶということの補足
C言語:「学問」と「実務」
統合開発環境
C言語ってポータブルですか?
C言語ってポータブルですか?:2
あなたは、どう読みますか?
ああ勘違い
試してガッテン
低級品
質問をするということ
ポカ
「何もしない」 != 「無駄」
エディタの話
もっと手を抜こう

いまどきの、アセンブラ

VisualStudio2005
VisualStudio2005:2

戦争の防ぎ方、に対する私の考え
身近な差別
改革
地球に優しいなんて大嘘
統計で嘘をつく方法

言葉について
言葉について:2

神が死んだということ
善悪の彼岸から、力への意思を目覚めさせるということ

本を読むということ
本を読むということ:2

絵を描く話
地図
地球儀

オカルトのお話
がんだむさん
RPGソフトウェア
記紀神話の不思議



メニュー

※上記の広告は60日以上更新のないWIKIに表示されています。更新することで広告が下部へ移動します。

C言語では、関数外にグロ-バル変数、関数内にローカル変数を宣言して使用することができます。
また、それらの変数の宣言の際に、staticやconstといった修飾子をつけることができます。

これはC言語にかぎらず、C言語以降の高級言語といわれているものではほとんど実装されている機能です。

これらは、コンパイルされた実行ファイル(マシン語に落とされた状態)ではどのような扱いになっているのでしょうか。


グローバルな変数は、関数にかかわらずそのプログラムが実行されている間はその領域に変更がないため、「ヒープエリア」と呼ばれる領域に確保されます。

コンパイラの実装やOSなどの処理系にも依存しますが、プログラムが実行される時に領域が確保されるわけです。

ローカル変数でstaticをつけて宣言した場合も、グローバル変数と同じ領域に確保されます。この場合もその領域は当該の関数を抜けても介抱されることはなく、再び当該関数に突入したときには以前の値を保持しています。使い方にもよりますが、関数の再突入性を悪くする恐れがあるので多様は避けたほうがいいでしょう。

また、mallocなどで確保したメモリもヒープエリアに確保されます。

これに対して、関数内の(非staticな)ローカル変数はスタック領域に確保されます。



・・・このあたりまでって、大体実はちょっと詳しい解説書には書いてあるんですよね。

でも実際に関数コールの際にスタックがどう振舞うか、とか、そもそも「スタック」ってなんですか、なんて感じの人が多い気がします。

というわけで長い前フリだったわけですが、関数・変数とスタックの関係について。


とりあえず、まずは「スタック」って言うものについて。

直訳すると、「棚」。値を入れたり出したりできる棚をスタックと呼びます。だいたいはCPU命令としてサポートされています。
PUSHでスタックに保存、POPでスタックから取り出します。

後入れ先だし(first in last out)なので、例えば
PUSH A
PUSH B
PUSH C
とすると取り出す際にはC、B、Aの順で取り出されるわけです。

以上がだいたいのスタックについてのお話。


もう少し詳しく説明すると、CPUにはスタックポインタと呼ばれるアドレスを保持するレジスタが居てまして、
PUSH AとCPUに命令するとCPUは
①まずレジスタのアドレスを進める(もしくは戻す)
②Aという値をそのレジスタに格納されているアドレスのメモリに保存する
というわけです。POP命令の場合は、その反対ですね。

スタックの格納先は当然メモリであり、プログラムごとにスタックとして利用できる領域があるわけです。

それが、「スタックエリア」です。


んで、話が長くなっちゃったわけなんですけどこのスタックがローカル変数とどう関係があるのか。

C言語では、結構気軽に関数コールできるわけですが、それがコンパイルされてマシン語に落ちたものを見てみると、関数一個呼ぶのでも結構がんばっているのですよ。
int hoge(int a)
{
  int b;
  b = a + 1;
  printf("%d%d\n", a, b);
  return b;
}
なんて、まったく意味のない関数があったとします。

この場合でも、まず
①汎用レジスタなどをPUSH
②現在のプログラムカウンタ(まさに今実行しているマシン語命令の次のアドレス)をスタックにPUSH
③関数に引数として渡された値をPUSH
 (上の場合ならaの値をスタックにつむ)
④hogeというサブルーチンへジャンプ
 (hogeのマシン語命令のあるアドレスをプログラムカウンタに入れる)
⑤スタックポインタをひとつ進める、あるいは戻す
 (この領域をローカル変数bとして使う)
・・・ここまでで、やっと関数の入り口にたどり着きました。

んで、
⑥スタックからaの値を取り出す
⑦プラス1する
⑧bの領域(スタックに確保した)に格納する
⑨再び①~⑤と同じことをやってprintf()コール処理をする
⑩アキュムレータ(処理を行うための特別なレジスタ)にbを格納する
 (戻り値とするため)
⑪スタックから元のプログラムカウンタを取り出し、上書き
・・・ここまでが関数内の処理。

とまあ、スタックを使って引数のやり取りや関数から抜けたときの戻り先、はては関数内のローカル変数の領域までいろいろな情報を保存しているのである。

このスタックはマルチタスクな環境ではプログラムごとに割り当てられているわけだけど、メモリの領域なので当然有限な領域でしかない。

したがって、ローカル変数ででかい配列を持とうとしたり、再帰処理などで終了条件を満たさないでずっと自分自身をコールし続けたりすると当然おかしなことになる。大抵は、別のプログラムのスタック領域などにはみ出して、そこに変な値を書き込んでしまうことになる。

また、関数を抜けるとPUSHした分スタックを戻して別の関数で再びローカル変数として使われる。C言語などで初期化されないローカル変数の値が不定とされるのは、その為だ。


こういった、「実際にマシン語になったときどうなっているのか」という知識って、必要だと思う。



※追記
ここまで読んで、「あれ?」って思ってくれた方はいると思います。
スタックってひとつずつしか格納/取り出しできないと書いたのに、関数の説明でローカル変数として使ってるのは無理があるんじゃないか、と当然思われるはずです。
答えは簡単で、スタックは単なるメモリなので、スタックポインタレジスタに入っているアドレスをまさにC言語のようなポインタとして扱ってやることでその前後の値は通常のメモリアクセスと同様にできるのです。「出し入れがひとつずつ」なのは、あくまでPUSH/POP命令による場合です。




| 新しいページ | 編集 | 差分 | 編集履歴 | ページ名変更 | アップロード | 検索 | ページ一覧 | タグ | RSS | ご利用ガイド | 管理者に問合せ |
@wiki - 無料レンタルウィキサービス | プライバシーポリシー