今までの学習では、gccコマンドを使用してプログラムを作成してきました。
gccを使用すると、いきなり実行ファイル(.exeファイル)が生成されていたのですが、gcc(というかCコンパイラ)は内部的に以下の手順でexeファイルを作っています。
.cファイル ↓ プリプロセス ↓ .iファイル ↓ コンパイル ↓ .sファイル ↓ アセンブル ↓ .oファイル ↓ リンク ↓ .exeファイル |
上の表の見方ですが、例えば一番上のところの記述は、”.cファイル”に対して、”プリプロセス”処理を行うと、”.iファイル”が生成されるという事を意味しています。
今まで行っていたような”gcc -o test01.exe test01.c”というコマンドは、コマンド1つ打つだけで”プリプロセス,コンパイル,アセンブル,リンク”の作業を一気に行ってくれています。
それでは、各ステップを順番に行う方法は無いかというと、gccコマンドにコンパイルオプションを指定することで、1ステップづつ処理を進めることも可能です。
それぞれのステップを行うためのコマンドは以下の通りです。
プリプロセスだけ行う gcc -E test01.c > test01.i コンパイルだけ行う gcc -S test01.i アセンブルだけ行う gcc -c test01.s リンクだけ行う gcc test01.o |
それでは、実際にcygwinで各処理を順に行ってみます。
今回、確認に使用するプログラムは以下のソースです。
#include<stdio.h> int main(void) { printf( "hello world\n" ); } |
プリプロセスだけ行う
(.iファイルが生成される)
$ ls ./ ../ test01.c $ gcc -E test01.c > test01.i $ ls ./ ../ test01.c test01.i |
コンパイルだけ行う
(.sファイルが生成される)
$ gcc -S test01.i $ ls ./ ../ test01.c test01.i test01.s |
アセンブルだけ行う
(.oファイルが生成される)
$ gcc -c test01.s $ ls ./ ../ test01.c test01.i test01.o test01.s |
リンクだけ行う
(.exeファイルが生成される)
$ gcc test01.o $ ls ./ ../ a.exe test01.c test01.i test01.o test01.s $ ./a.exe hello world |
というわけで、確かに1ステップづつ処理を行わせる事が出来ました。
ファイルの中身をのぞいてみると、(意味は分からないかもしれませんが)各ステップでどんな感じの成果物が出来上がっているのか、イメージできるかと思います。
と、ここまでが前置きです。
それでは、本題のプリプロセス命令を使用したプログラムを作成してみます。
#include<stdio.h> #define GOOD_SCORE 80 void print_result( int ); int main(void) { print_result( 94 ); print_result( 50 ); return 0; } void print_result( int score ) { printf( "あなたの点数は%d点です\n", score ); if ( score > GOOD_SCORE ) { printf( " 合格です\n" ); } else { printf( " 不合格です[合格まであと%d点でした]\n", GOOD_SCORE - score ); } } |
これをコンパイル&実行すると、以下の結果になります。
$ gcc test01.c $ ./a.exe あなたの点数は94点です 合格です あなたの点数は50点です 不合格です[合格まであと30点でした] |
今回のプログラムでは、2行目の”#define”がプリプロセッサ命令です。
(プリプロセッサ命令は、プリプロセッサディレクティブと呼ぶ場合もあります)
define命令は、指定された文字の置換を行ってくれます。
#define 置換前の値 置換後の値 |
置換がどのように行われているかを確認するために、”gcc -E”でプリプロセスだけ行ってみます。
$ gcc -E test01.c > test01.i $ cat test01.i # 1 "test01.c" # 1 "<built-in>" # 1 "<command-line>" # 1 "test01.c" # 1 "/usr/include/stdio.h" 1 3 4 # 29 "/usr/include/stdio.h" 3 4 # 1 "/usr/include/_ansi.h" 1 3 4 ... 途中1100行ほど省略 ... __c = __c2; else ungetc(__c2, __p); } return __c; } # 683 "/usr/include/stdio.h" 3 4 # 2 "test01.c" 2 void print_result( int ); int main(void) { print_result( 94 ); print_result( 50 ); return 0; } void print_result( int score ) { printf( "あなたの点数は%d点です\n", score ); if ( score > 80 ) { printf( " 合格です\n" ); } else { printf( " 不合格です[合格まであと%d点でした]\n", 80 - score ); } } |
作成されたtest01.iを見ると1200行以上の膨大なファイルになってしまいました。
この内、最初の1100行ぐらいはよくわからないプログラムが出力されていますが、いったん無視します。
注目してもらいたいのは最後の部分で、print_result()関数を見ると、先ほどtest01.cファイルででGOOD_SCOREと書かれていた個所が80に置換されていることが確認できます。
このように、#defineはコンパイルの前に置換処理を行ってしまいます。
パッと見た感じだとGOOD_SCOREという変数を作っているように見えますが、defineの違うところは”コンパイルの前”にこの作業を行っている点です。
プリプロセッサ命令は#defineだけではなく、他にも以下のようなものがあります。
#include #line #pragma #define #undef #error #if #ifdef #ifndef #else #elif #endif |
今までプログラムの1行目に”おまじない”として書いていた#includeですが、これもプリプロセッサ命令です。#includeの意味は、”その後に書かれたファイルの中身で#includeのある行を置き換える。”という意味です。
先ほど1100行ほど良く分からないソースがくっついていましたが、これはstdio.hというファイルの中身になります。
(stdio.hはコンパイラをインストールしたフォルダに有り、cygwinだと/usr/includeの下に存在します)
プリプロセス命令の位置づけ(再確認)
というわけで、プリプロセス命令というのは、厳密にいうとC言語ではありません。
(“C言語ではない”と書くと、プリプロセッサは不要なのか?と思われるかもしれませんが、実際にはほぼ全てのコンパイラで利用されています)
最初に書いたコンパイル処理の流れでいうと、コンパイラが解釈するのがC言語で、プリプロセッサ命令はC言語のソースを分かりやすく書くための記法です。
関連記事
コメントを残す