[C言語入門]プリプロセッサ命令を理解する

今までの学習では、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言語のソースを分かりやすく書くための記法です。

関連記事

コメントを残す

メールアドレスが公開されることはありません。