この記事は、絵でわかる プログラムとは何か(3)~リンクで進化した言語処理~ の最初に読んでいただくべき記事です。
この記事のポイント
- プログラムを効率よく作るための、アセンブリ部品を、モジュールという。
- モジュールは、モジュール名でルーチンを呼び出せるようにしたもの。
- モジュール名からのルーチン復元方法に、インライン展開とリンクの2つがある。
- ソースコードに、ルーチンのコピーを挿入するのがインライン展開。
- ソースコードにラベル、call、プロローグ、エピローグを記載をして、ルーチン参照をするのがリンク。
- よく使うモジュールを、ニーモニック一つで呼び出せるようにしたのがマクロ。
プログラミングの生産性を高めよう
コンピューターの利用機会が増えるほど、プログラムを作る機会も増えました。そして、プログラムを作る機会が増えるほど、次は「いかに効率良くプログラムを作れるか」というプログラミングの生産性に焦点が移ります。
設計の効率化とコーディングの効率化
プログラムを効率良く作るためには、2つの課題がありました。
- プログラムの記述(コーディング)前の、設計(プログラムデザイン)の負担を減らすこと。
- プログラムの記述(コーディング)量、そのものを減らすこと。
解決策としての高級言語とモジュール化
そして、その解決策として、
- プログラムの設計負担を減らすために、数々の高級プログラミング言語が作られました。
- プログラムの記述量を減らすために、モジュール化が進みました。
高級プログラミング言語については、前回の記事で説明しました。ここでは、モジュール化について、見ていきましょう。
モジュール化
効率良くプログラムを作るためには、複雑なプログラムをいかに単純な構成で実現できるかがカギになります。複雑と単純の両立で、一見、矛盾するようですが、これを可能にするのがモジュール化(部品化)という考え方です。
モジュール名への変換/モジュール名からの復元
複雑な処理部分をモジュール名に変換
主にルーチンをモジュール化
プログラムは、長く複雑になるほど、その中で同じような処理が何度も出てくるようになります。何度もプログラム中に再登場するような、よく行われる処理のことをルーチン(routine)と言います。
本来の英語では、どちらかと言うと「ルーティーン」に近い発音です。日々の定例作業のことをルーティンワーク(routine work)と言うのは、日本語(ビジネス用語)でも定着しつつありますが、それと同じ routine です。
このようなルーチンを、ひとかたまりの部品 = モジュール(module) としてまとめ、簡単に使い回せるようにすることをモジュール化と言います。モジュールの1つ1つには、わかりやすいモジュール名(部品名)を付けておきます。
モジュール化してあれば、プログラム中でまた同じ処理が必要になっても、モジュール名(部品名)を一行書いておくだけで、あとでそのモジュールの処理をそこに復元できるのです。
モジュール化の効用
再登場しない一度きりの処理(非ルーチン)に対しても、複雑な処理をひとことで言い表すモジュール名を付ければ、そこで行われる処理をイメージしやすくなります。
モジュール化すると、プログラム中のモジュール部分(ある程度複雑な記述部分)が、モジュール名1つに置き換えられるため、全体の見通しを単純化することができます。
また、大規模なプログラムであっても、モジュールに分けて、モジュール単位でファイルを分ければ、作業分担や問題箇所の特定・修正がしやすくなります。
あいまいな概念としてのモジュール
モジュールは、適用範囲や使い方などによって、サブルーチン(subroutine)、プロシージャ(procedure)、関数(function)、コンポーネント(component)などとも言われます。
また、それらの言葉が表す意味も、プログラミング言語によって多少異なります。
ここで述べているモジュールは、個々のプログラミング言語等によって明確に規定された意味のモジュールではなく、広い意味でのプログラム部品としてのモジュールを指しています。
モジュール化する処理の規模や用途に決まりはなく、1行だけの簡単な処理から、複数の処理をまとめた複雑な処理まで、用途に応じて様々な範囲の処理をモジュール化できます。親モジュールの中にいくつかの子モジュールというように、モジュールを階層化することもできます。
ただ、モジュールとしてのレベル(モジュール性)は、一部品としてきれいに他と分離できるほど、高いものと評価されます。
モジュール名を実際の処理に復元
モジュールの復元主体
では、モジュール名から実際の処理への復元は、誰がするのでしょうか?
プロセッサは、マシンコードによって論理回路の切り替えができる集積回路でしかありません。したがって、その切り替え指示は、すべてマシンコードプログラムに書かれていなければなりません。
つまり、
人間が作ったモジュール名の混じったソースコードを、マシンコードへ変換するプログラム
=
アセンブラ や コンパイラ
に、モジュール名を、実際の処理のマシンコードへ復元するしくみを入れるのです。
モジュールの復元方法
具体的な復元の実現方法としては、インライン展開(inline expansion)とリンク(link)の2つがあります。
inlineとは、プログラム文の行(line)の、中(in)に、という意味です。
- インライン展開
- モジュール名の位置に、モジュールの処理内容をそのまま復元(コピー)する
- リンク
- モジュール名の位置から、すでに処理内容が記録されたメモリ領域へジャンプさせる
どちらの方法が良いかは、その時々の状況によって変わります。
- インライン展開にすると、リンクよりもプログラム全体の文字数(メモリ使用量)が多くなります。
- リンクにすると、インライン展開よりもジャンプする分だけプログラム実行時間が長くなります。
先ほどの「お祈り(二礼二拍手一礼)」の例では、インライン展開が行われていました。インライン展開は、次のリンクよりだいぶ単純です。
対して、リンクの動作は少し複雑です。最初から細かい流れを覚える必要はありませんので、以下の「リンク」の章はざっと読み飛ばしていただくだけでも構いません。ただ、余裕がある時にじっくり流れを追っていただくと、複雑な処理も当たり前のことをしているだけというのがわかるかと思います。そうした経験こそ、プログラミングの本当の理解につながります。
リンク
アセンブリ言語でのリンクは、(1)モジュールへのラベル付け と、(2)本体からラベルへの参照 と、(3)モジュールからの戻り によって作られます。具体的には以下のように、コールスタックを利用して、標的となるメモリアドレスをあるべき順番どおりに記録/取得します。
(1) ラベル = モジュール名 をつける
モジュールの作成者が、モジュールの先頭に、ラベルを付けます。
アセンブリ言語では、ラベル名(ラベルの「:」より手前につけた文字列)がモジュール開始メモリアドレスの代名詞になり、アセンブル時にモジュール開始メモリアドレスに変換されます。
したがって、ここで付けたラベル名がアセンブリ言語でのモジュール名に相当します。
(2) ラベル参照(reference)
アセンブリ言語でプログラミングする時、モジュールの処理をさせたい部分に、「 call ラベル名 」と記述し、モジュール開始メモリアドレスへジャンプさせます。これを参照(reference)と言います。
call命令では、モジュール処理が終わった後に戻るべき命令のメモリアドレス(リターンアドレス)を、プログラムカウンタからコールスタックへ退避(push)し、代わりにモジュール開始メモリアドレスをプログラムカウンタへ上書きして、そこへジャンプさせます。
(3) 戻り(de-reference)
モジュールのソースコードには、必ず本体プログラムへプロセッサが戻れる仕掛け(dereference)を入れておきます。
その仕掛けとは、モジュールの冒頭と末尾それぞれに、決まった2行を入れておくことです。(「引数」がある場合は、さらにもう少し複雑になりますが、ここでは割愛します。)
モジュールの冒頭(プロローグ:prolog)
- ベースポインタにある本体用スタックフレームのベースアドレスを、コールスタックに記録しておくpush行
- スタックポインタにあるトップアドレスを、モジュール用スタックフレームのベースアドレスとしてベースポインタへ上書きするmov行
モジュールの末尾(エピローグ:epilog)
- コールスタックにある本体用スタックフレームのベースアドレスを、ベースポインタへ上書きするpop行あるいはleave行
- コールスタックにあるリターンアドレスをpopし、プログラムカウンタに入れてそこへジャンプさせるret行
以上の仕掛けをプログラムに入れておくと、本体プログラムとモジュールの間の切り替えで、次図のような動きになります。
このような本体とモジュール間での、アドレス指定によるメモリジャンプが、リンク(link)の正体です。アセンブリ言語は、基本的にはマシンコードと1対1対応しているものなので、高級言語をコンパイルして生成されるマシンコードのリンクにも、このような仕掛けが入れられます。この仕掛けルールを呼出規約(calling convention)と言います。
マクロ
アセンブラでは、よく使われるモジュール(ルーチン)をどのプログラムでも簡単に利用できるよう、アセンブラ自体の中にルーチンのマシンコードプログラムを用意しておき、そのルーチン専用に新たなニーモニック(疑似命令)が割り当てられるようになりました。このニーモニック1行で書き表せるようにしたルーチンをマクロ(macro)と言います。
通常のニーモニックはマシンコードの最小単位であるオペコード(マイクロプログラム)と1対1に対応するものですが、それに対して、新たに追加した1つのニーモニックに、何ステップかのオペコードを(1対多で)対応させたものが、マクロプログラム(マクロ)になります。
なお、マクロという用語は、アセンブリ言語での疑似命令に限らず、アプリでの「操作手順おまとめ機能」を表す言葉としても使われています。アプリの通常の1操作(ミクロ)に対して、複数の操作手順をひとまとめにしたボタンやメニューも、疑似命令からの類推でマクロと言います。さらに、このまとめ機能をより詳細に作り込むために使われる高級言語のことをマクロ言語と言います。例えば、Excelなどで使われるVBAはマクロ言語の一つです。
PCSプログラミングスクールの Excel VBA コースはこちら
この記事のまとめ
プログラムを効率よく作るために、アセンブリの部品化=モジュール化が進みました。
モジュールは、頻繁に必要となる処理(ルーチン)に名前=モジュール名を付け、モジュール名でルーチンを呼び出せるようにしたものです。
呼び出すモジュール名から、実際の処理への復元方法として、インライン展開とリンクの2つがあります。
ソースコードの、モジュール名が書かれた位置に、実際の処理のコピーを挿入するのがインライン展開で、実際の処理のコピーではなく、実際の処理への参照を挿入するのが、リンクです。
リンクの流れは少し複雑ですが、ソースコードに以下の記載をしておけば、あとはアセンブラが処理をしてくれます。
- モジュール名として使うラベル
- ラベル参照時に、戻り位置をコールスタックへ追加する call
- モジュールのスタックフレームをコールスタックに追加するプロローグ:push, mov
- モジュールのスタックフレームを削除し、コールスタックから戻り位置を復元するエピローグ:pop, ret
また、よく使われるモジュールは、プログラマが個別に用意しなくても、ニーモニック一つで呼び出せるマクロとして、アセンブラに同梱されるようになりました。
次は、コンパイラの進化 に進みましょう。
この記事は、絵でわかる プログラムとは何か(3)~リンクで進化した言語処理~ の一記事です。
コンピューターのしくみ全体を理解したい場合は、以下の2コースがお勧めです。
日本全国 オンラインレッスン にも対応しています。
知りたいことだけ単発で聞きたい場合は、 オンラインサポート をご利用ください。