
■ セグメント・call・ret ■
前回はスタック領域とその動作について説明しました。
スタック領域というのはメモリ上にあり、「積み重ねるように
して」データを格納する場所の事を言うのでした。
「スタック領域の1番上」の事を「スタックポインタ」と呼び、
「スタックポインタ」は「ESP」レジスタに格納されている事
も説明しました。
また「ESP」レジスタの値(スタックポインタ)は、スタック
領域にデータを格納する命令「push」やスタック領域からデー
タを取り出す命令「pop」を使用する事により、自動的に加減
算される事も説明しました。
「pop」命令と「push」命令の動作イメージをもう1度見てみ
ましょう。
◆pop ebxの動作イメージ
1.スタック領域からデータを取り出す
レジスタEBX
┌──────┐
pop ebx┌→│ 5 │
│ └──────┘
│
スタック領域│ │ │
├──────┤
│ 5 │
├──────┤
│ 4 │
├──────┤
│ 3 │
├──────┤
│ 2 │
├──────┤
│ 1 │
└──────┘
2.ESP(スタックポインタ)レジスタに4加算
レジスタESP
┌──────┐
│ │←──「+4」
└──────┘
「pop ebx」という命令を使用する事により、スタック領域か
らデータを取り出した後に自動的に「ESP」レジスタが4加算
されています。
「EBX」レジスタは4バイトの大きさがあるので4加算される
のでした。
今度は「push」命令の例を見てみましょう。
◆push 5の動作イメージ
1.ESP(スタックポインタ)レジスタから4減算
レジスタESP
┌──────┐
│ │←──「−4」
└──────┘
2.スタック領域にデータを格納
push 5─┐
│
│
スタック領域│ ↓ │
├──────┤
│ 5 │
├──────┤
│ 4 │
├──────┤
│ 3 │
├──────┤
│ 2 │
├──────┤
│ 1 │
└──────┘
「push」命令ではスタック領域にデータを格納する前に「ESP」
レジスタが減算されています。
これらの動作が行なわれる事により「ESP」レジスタは常にス
タック領域の1番上を示す事が出来るのでした。
前回の説明で関数にパラメータを渡す事が出来るようになった
ので、今回は関数を「呼出す」事について説明したいと思いま
す。
いきなり答えから言うと、インテルプロセッサで関数を呼出す
為には「call」という命令を使用します。
例えば、「sub」という関数を呼出すには
call near ptr sub
と書きます。
間に「near ptr」という記述があります。
call near ptr sub
~~~~ ~~~
↑
ニア・ポインタと読みます。
これが付いていると、「subという関数は近くにありますよ。」
という意味になります。
「近くってどこ?」という事になると思うので説明します。
皆さんが使用しているパソコンの中には128メガ、256メ
ガ、512メガ等、環境によりさまざまだと思いますが、必ず
メモリが搭載されているはずです。
このメモリの中で、例えばここからここまではプログラムの実
行コードを格納する場所。ここからここまではデータを格納す
る場所、等と「領域」が決められています。
以下にメモリイメージの例を示します。
メモリ
┌ ┌──────────┐
│ │ │
│ │ │
│ │ │
│ │ │
│ ├──────────┤
│ │プログラム実行コード│
│ │ │
│ ├──────────┤
│ │ │
256メガ│ │ │
│ │ │
│ │ │
│ │ │
│ ├──────────┤
│ │ │
│ │ データ │
│ │ │
│ ├──────────┤
│ │ │
│ │ │
│ │ │
└ └──────────┘
上の例は全メモリサイズが256メガバイトですが、あくまで
1例です。環境によっては128メガだったり512メガだっ
たりする場合もあると思います。
また、プログラム実行コードが格納されている領域だけを見て
みても、ここからここまではアプリケーションAのプログラム
実行コードを格納する場所。ここからここまではアプリケーシ
ョンBのプログラム実行コードを格納する場所。等と領域が分
けられています。
以下にプログラム実行コード部が更にいくつかの領域に分かれ
ているイメージを示します。
プログラム実行コード格納領域
┌──────────┐
│ │
│アプリケーションA │
│ │
├──────────┤
│ │
│アプリケーションB │
│ │
├──────────┤
│ │
│ │
│ │
│ │
└──────────┘
このようにメモリは複数の「領域」に分けられているのですが、
この「領域」の事を「セグメント」と言います。
インテルプロセッサは「セグメント単位に」保護をかけます。
例えば、アプリケーションAのプログラム実行コードから、ア
プリケーションBの関数を呼出す事は基本的に出来ません。
プログラム実行コード格納領域
┌──────────┐
│ │
│アプリケーションA ├──┐
│ │ │
├──────────┤ ×CALL NG
│ │ │
│アプリケーションB │←─┘
│ │
├──────────┤
│ │
│ │
│ │
│ │
└──────────┘
何故かというと、「セグメント」単位にインテルプロセッサに
より保護がかけられているからです。
同一セグメントの関数を呼出す事はいつでも好きな時に出来ま
す。
プログラム実行コード格納領域
┌──────────┐
│ ├──┐
│アプリケーションA │ │CALL OK
│ │←─┘
├──────────┤
│ │
│アプリケーションB │
│ │
├──────────┤
│ │
│ │
│ │
│ │
└──────────┘
そしてこの「同一セグメント」内の関数呼出しを行う時に「近
くにありますよ。」という意味の「near ptr」という記述をし
てあげます。
call near ptr sub
~~~~ ~~~
このようにsubという関数は近く(同一セグメント)にありま
すよ、というのをプロセッサに教えてあげているのです。
少し分かりにくいかもしれませんが、メモリというのは複数の
領域(セグメント)に分けられており、プロセッサはセグメン
ト単位に保護をかけているのだという事だけ覚えておきましょ
う。
以下に「call」命令を使用して関数を呼出しているイメージを
示します。
┌──────────────────────────┐
│ アプリケーションソフトウェア │
│ │
└───┬──────────────────────┘
│ 呼出し
│call near ptr sub ┌────────┐
├────────→│ sub関数 │
│ │ │
│ └────────┘
上のイメージでは「sub」という関数を呼出しています。
この「sub」関数ですが、処理が終わった後に元の場所に戻
らなければなりません。
以下にsub関数が処理を終わって、元の場所に戻っているイ
メージを示します。
┌──────────────────────────┐
│ アプリケーションソフトウェア │
│ │
└───┬──────────────────────┘
│ 呼出し
│call near ptr sub ┌────────┐
├────────→│ sub関数 │
│←──┐ │ │
│ │ └───┬────┘
│ │ │
続きの処理 │ ┌──┴──┐
・ │ │ 処理 │
・ │ └──┬──┘
・ │ 呼出し元へ戻る │
└─────────┘
上のイメージで、「呼出し元へ戻る」というのがありますが、
これもアセンブラ言語の命令です。
この「呼出し元へ戻る」というのをアセンブラ言語で表すと、
「ret」となります。
「ret」とは、return(戻る)の略です。
ここで皆さん注目して頂きたいのですが、「呼出し元へ戻る」
の「呼出し元」ってどこなのでしょうか?
subという関数は、さまざまな場所から呼出される可能性が
あります。
例えばアプリケーションソフトウェアの1番最初にsub関数
が呼出されたとすると、「ret」命令により、1番最初の方に
処理が戻ります。
また、例えばアプリケーションソフトウェアの1番「最後」に
sub関数が呼出されたとすると、「ret」命令により、1番
「最後」の方に処理が戻ります。
この様に1口に「呼出し元」と言っても、その時々によってさ
まざまな場所を示すのです。
以下にアプリケーションソフトウェアの1番最初の処理でsu
b関数を呼出しているイメージを示します。
┌──────────────────────────┐
│ アプリケーションソフトウェア │
│ │
└───┬──────────────────────┘
│ 呼出し
1番最初│call near ptr sub ┌────────┐
├────────→│ sub関数 │
│←──┐ │ │
│ │ └───┬────┘
│ │ │
続きの処理 │ ┌──┴──┐
・ │ │ 処理 │
・ │ └──┬──┘
・ │ 1番最初に戻る │ret命令
└─────────┘
「呼出し元」が1番最初の方にあるのでsub関数の処理が
終了したら1番最初に戻っています。
この「1番最初に戻る」というのをアセンブラ言語で書くと
ret
という、たったこれだけの記述になります。
今度は、アプリケーションソフトウェアの1番最後の方でsu
b関数を呼出しているイメージを示します。
┌──────────────────────────┐
│ アプリケーションソフトウェア │
│ │
└───┬──────────────────────┘
│
│ ┌────────┐
│ ┌─→│ sub関数 │
│ │ │ │
│ │ └───┬────┘
│ │ │
│ └──┐┌──┴──┐
│ ││ 処理 │
│ │└──┬──┘
│ │ │ret命令
│ │ │
│ │ │
│ │ │
│ │ │
│ 呼出し │ │
1番最後│call near ptr sub │ │
├─────────┘ │
│←────────────┘
処理終了 1番最後に戻る
「呼出し元」が1番最後の方にあるのでsub関数の処理が
終了したら1番最後に戻っています。
この「1番最後に戻る」というのをアセンブラ言語で書くと
ret
という、たったこれだけの記述になります。
さて、「1番最初」に戻るのも、「1番最後」に戻るのも、ア
センブラ言語で書くと
ret
という、たったこれだけの記述になってしまいました。
何故、全く同じ命令なのに戻る場所が異なるのでしょうか?
それは、関数を呼出す時にどこに戻れば良いかの情報をスタッ
ク領域に格納しているからです。
誰がスタック領域に格納してくれるのかと言うと、インテルプ
ロセッサが自動的に行なってくれます。
つまり関数を呼出す時には、同時にスタック領域に「どこに戻
れば良いか」の情報も「ハード的に」格納してくれるのです。
以下に「call」命令の動作イメージを示します。
先ほど見て頂いたイメージとは、「call」命令実行時と「ret」
命令実行時の記述にインテルプロセッサ(ハード)が行なう処
理の記述が追加されています。
┌──────────────────────────┐
│ アプリケーションソフトウェア │
│ │
└───┬──────────────────────┘
│ 呼出し スタック領域
1番最初│call near ptr sub ┌─────┐
├─────────────────────→│1番最初 │
│ ┌────────┐ └─────┘
├────────→│ sub関数 │
│←──┐ │ │
│ │ └───┬────┘
│ │ │
続きの処理 │ ┌──┴──┐
・ │ │ 処理 │
・ │ └──┬──┘
・ │ │ret命令 スタック領域
・ │ │どこへ戻るか参照┌─────┐
│ 1番最初に戻る │←───────┤1番最初 │
└─────────┘ └─────┘
call命令によりsub関数が呼出される前に、スタック領域に
「どこに戻れば良いか」の情報が格納されています。
スタック領域への格納完了後にsub関数に処理が移行されて
います。
「call」というたった1つの命令で、インテルプロセッサはこ
れらの事をハード的に行なってくれるのです。
そしてsub関数の処理が完了したら「ret」命令によりスタ
ック領域が参照されています。
スタック領域から「どこへ戻れば良いか」の情報を参照後に、
呼出し元に処理が移行されています。
「ret」というたった1つの命令で、インテルプロセッサはこ
れらの事をハード的に行なってくれるのです。
今回は関数を呼出す時や元の処理に戻る時に何が行なわれるの
か説明しました。
次回は実際に関数を作ってみて、関数がどのように使われるの
か見てみようと思います。