Changes between Version 2 and Version 3 of HowTo/CTutorial


Ignore:
Timestamp:
Sep 4, 2010, 11:00:30 AM (14 years ago)
Author:
村山 俊之
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • HowTo/CTutorial

    v2 v3  
    137137
    138138…といっても、心配することはありません。例えば、上記サンプルプログラムにおいても、画面に文字を表示するのに、 printf や puts といった名前の関数を利用していますが、これらはプログラマーが自分で定義した関数ではなく、 C コンパイラーを作った人々 (ベンダー) が用意している'''標準ライブラリ'''において用意されている関数です。標準ライブラリにどのような関数が用意されているかは、 ANSI や ISO などによって標準規格として定められており、それらの関数を用いる範囲内においては、ある程度の可搬性 (即ち、 OS や C コンパイラが変わっても、コンパイルが通り、同じような動作をしてくれること) が期待できるのです。
     139
     140=== コンパイル言語 ===
     141
     142C 言語はコンパイル言語です。 C コンパイラが C 言語で書かれたプログラムを実際にコンピュータ上で動作するプログラム (いわゆる「実行ファイル」と呼ばれるもの) に変換するまでの伝統的な手順は概ね以下の通りです。
     143
     144 1. '''プリプロセッサ'''により、番号記号 "#" で始まる行を処理する (ファイル挿入や行削除など)。
     145 1. '''コンパイラ'''により、 C 言語で書かれたプログラムを、アセンブリ言語のプログラムに変換する。
     146 1. '''アセンブラ'''により、アセンブリ言語のプログラムを機械語のバイナリファイルに変換する。
     147 1. '''リンカ'''により、関数や変数などの名前を頼りにバイナリファイルをつなぎ合わせ、実行可能なプログラム (実行ファイル) に変換する。
     148
     149この動作を知っていることは重要です。プリプロセッサの役割を理解していることは、マクロなどのプリプロセッサ行をどう活用すべきかを考える指針になりますし、コンパイラがアセンブリ言語のプログラムを生成できることを知っていれば、動作効率を最適化するための参考になるでしょうし、コンパイルとリンクが処理の段階としては別個であることを理解していれば、リンクエラーを解決する手立ての見当がつくようになります。
     150
     1511 の'''プリプロセッサ'''は、プログラム中の番号記号 "#" で始まる行 (プリプロセッサ行) を「先に」処理しておくツールです。先ほどのサンプルプログラムで言うと、 #include で始まる行や、 #define で始まる行がそれに該当します。プリプロセッサが #include で始まる行を見つけると、そこに書かれているファイル名のファイルを探してきて、その行の位置に挿入します。また、 #define で始まる行を見つけると、その直後に書かれた名前と同じ単語を、その更に後ろに書かれた内容で置換します (例えば、 "MAX_CALC_NUM" と書かれた箇所が、事前に "1000" に書き換えられます)。
     152
     1532 の'''コンパイラ'''は、C で書かれたプログラムを、あくまでアセンブリ言語のプログラムに置き換えるだけです。実は、 C 言語はアセンブリ言語に割と置き換えやすいように設計されています。但し、ただ単に置き換えるよりも、工夫して置き換えた方が、動作効率が良くなったり、実行ファイルを小さくまとめられたりしそうな箇所については、コンパイラが独自に判断して、上手く変換してくれる場合もあります。使用するコンパイラや、コンパイル時に指定するオプションによって、プログラムの実行速度や、実行ファイルのファイルサイズなどが変わる場合があるのは、その為です。
     154
     155GCC でも、 -S オプションを指定することによって、アセンブリ言語で書かれたプログラムを生成することができます。以下のように実行すると、アセンブリ言語によるプログラムファイル test.s が生成されます。
     156
     157{{{
     158$ gcc -S test.c
     159}}}
     160
     161アセンブリ言語とは、機械語と呼ばれる、コンピュータが直接命令として理解できる数値の羅列を、その数値毎に名前をつけて、命令毎に行に起こしたものです。昔は機械語を直接コンピュータに入力していましたが、数値の羅列ではさすがにわかりにくすぎるので、命令毎に書き起こして分かりやすくしたのがアセンブリ言語でした。
     162
     163しかし、このアプローチはお世辞にも直感的とは言えません。アセンブリ言語には変数の概念が無く、値を書き込むメモリーの位置や、どのレジスタ[[FootNote(CPU などのプロセッサ (演算処理装置) が命令を処理する際、その命令にどの値を用いるかは命令の種類によって決まっていたり、あるいは命令と一緒に指定して決めたりします。その、命令を処理する際に用いる値を格納する場所がレジスタです。これに対し、メインメモリーは、あとでレジスタに取り込んで命令に用いるつもりで用意した値を保存しておく場所であり、メモリー上に記録されている値を直接命令によって処理できるわけではありません。)]]を用いるかなどは、プログラマーが自分で管理しなければなりません。また、高級言語では 1行の計算式で書き表せる演算を、アセンブリ言語では演算子毎に (演算順序を気にしながら!) 行を分けて書き連ねる必要があり、感覚的には非常に冗長です。
     164
     165例えば、以下の非常に簡単なプログラム
     166
     167{{{
     168#include <stdio.h>
     169
     170int main()
     171{
     172    int a[] = { 1, 2, 3, 4 };
     173    int b = 0;
     174   
     175    b = a[0] + a[1] * a[2] - a[3];
     176   
     177    printf("a = %d\n", b);
     178   
     179    return 0;
     180}
     181}}}
     182
     183のうち、以下の行
     184
     185{{{
     186    b = a[0] + a[1] * a[2] - a[3];
     187}}}
     188
     189は、アセンブリ言語に変換すると、以下のようになります[[FootNote(変換したアセンブリソースの抜粋です。なお、変換に用いた環境は、 Intel CPU を搭載した一般的な Windows XP パソコン上にインストールされた MinGW の GCC 4.5.0 です。)]]。
     190
     191{{{
     192        movl    28(%esp), %edx
     193        movl    32(%esp), %ecx
     194        movl    36(%esp), %eax
     195        imull   %ecx, %eax
     196        addl    %eax, %edx
     197        movl    40(%esp), %eax
     198        movl    %edx, %ecx
     199        subl    %eax, %ecx
     200}}}
     201
     202メモリー上に記録されている値をレジスタにコピーし、掛け算して、足し算して、更にメモリーからレジスタにコピーして、引き算する、といった処理内容です。この例の場合、 imull という 1ステップで掛け算を処理してくれる便利な命令があるのでまだ分かりやすいですが、もしもこの命令が搭載されていないコンピュータだった場合、足し算を複数回繰り返すループとして記述されていたでしょう。