D.プリプロセス命令(#で始まる行について)

 Copyright(C) 27Jan2003 coskx

このページではよく使われるプリプロセス命令(#で始まる行)について解説している。

内容は以下のとおりである。
1 #include "stdio.h" などの #include
2 #define による定数定義
3 #define による変数型定義
4 #define によるマクロ
5 #if による条件コンパイル

 D.1 #include

 「#include "stdio.h"」あるいは「#include <stdio.h>」について,Cを学び始めの頃は「おまじない」なのでこれの意味は追求しないこととしていた。
 「#include "stdio.h"」 とは,Cプログラムソース中で,「#include "stdio.h"」が書かれている位置に,「stdio.h」という名前のファイルを読み込んでから,コンパイルを始めなさいという意味である。ちなみにstdio.hは「standard I/O」(I/OはInput/Outputの意味)に関する必要な定義や関数のプロトタイプ宣言が書かれているヘッダファイル(header file)である。
「.h」はヘッダファイルを意味する。また「include」というのは「〜を(…の中に)含める」という意味である。
  「stdio.h」という名前のファイルはどこにあるかというと,どのコンパイラを使っているのかに依存するが,コンパイラのあるフォルダの近くにある 「include」というフォルダ内にある場合が多い。例えば,WindowsPCでファイル検索を全ハードディスクを対象に行うと見つけることが出来 る。数百行のテキストファイルであるが,この数百行が,自分で書いたプログラムソースの「#include "stdio.h"」が書かれている位置に読み込まれてからコンパイルが始まる。コンパイルの直前に行われる作業なので「プリプロセス」と呼ばれる

ところでコンパイルとは何かというと
Cプログラムソースを機械語に変換する最初の段階の作業のことである。通常はコンパイル作業後にリンクといって実行に必要な機械語コードを結合して実行可能なファイルが作られる。

どのような時に,このファイル「stdio.h」を必要とするかというと
(1)printfなどの画面表示とscanfなどのキーボード入力(コンソール入出力とも呼ばれる)の関数を呼ぶ時
(2)fopen,fprintf,fputs,fpuc,fcloseなどのファイルの入出力関係
である。

このほかに
#include "math.h" は数学関係の関数(sqrt,sin,cos,tan)を使う時
#include "stdlib.h" はexitなどの関数を使う時
#include "ctype.h" は文字処理関係の関数(isupper,toupper)を使う時
#include "string.h" は文字列処理関係の関数(strlen,strcpy,strcat)を使う時
に使用される。関数のライブラリマニュアルをみると,その関数を使う時にどのヘッダファイルを使うように宣言すればよいかわかる。

これらのヘッダファイルはコンパイラ供給元が,コンパイラの仕様の差を吸収するために使っていることもあるので,コンパイラAに付属しているヘッダファイルをコンパイラBでの使用時に使うことは出来ない。そのため,通常はヘッダファイルの中身を変更してはいけない。

 D.2 #define による定数定義

 Cプログラム中で,定数をそのまま書くとその定数の意味がわからなかったり,プログラムの定数変更時に,定数の変更忘れが起こる場合がある。これを防ぐために#define による定数定義を行なう。
 #define による定数定義はコンパイル作業の直前の作業であるプリプロセス時に,数値に差し替えられる。

例えば次の例でPIはプリプロセッサで3.14...に展開されてからコンパイルされる
List D.2.1 #defineでπを表現したプログラム

#include <stdio.h>

#define PI 3.141592653589793

int main()
{
    double radius;/*半径*/
    double area;  /*面積*/
    radius=10.0;
    area=PI*radius*radius;
    printf("radius=%f area=%f\n",radius,area);
    return 0;
}


radius=10.000000 area=314.159265
 
List D.2.2 プリプロセッサがPIを数値に展開したイメージ
通常はこの状態を見ることは出来ない
(コメントも取り払われている)

#include <stdio.h>

int main()
{
    double radius;
    double area;
    radius=10.0;
    area=3.141592653589793*radius*radius;
    printf("radius=%f area=%f\n",radius,area);
    return 0;
}

 

例えば次の例を見てみよう
List D.2.3 ソートのプログラム中の二重ループでスナップショット

#include <stdio.h>

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<4;i++) {
        for (j=i+1;j<5;j++) {
            printf("(%d,%d) ",i,j);
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
        printf("\n");
    }
    /*表示部*/
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}


173.2 168.5 178.1 183.7 164.2
(0,1) (0,2) (0,3) (0,4)
(1,2) (1,3) (1,4)
(2,3) (2,4)
(3,4)
183.7 178.1 173.2 168.5 164.2
 

ここで
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/

を10個のデータに変更したい場合,何箇所の変更が生ずるか考えてみると不便さがわかる。

このプログラムを次のように書き直しておくと
List D.2.4 ソートのプログラム中の二重ループでスナップショット

#include <stdio.h>

#define SIZE 5

int main()
{
    double height[SIZE]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<SIZE;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<SIZE-1;i++) {
        for (j=i+1;j<SIZE;j++) {
            printf("(%d,%d) ",i,j);
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
        printf("\n");
    }
    /*表示部*/
    for (i=0;i<SIZE;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}


173.2 168.5 178.1 183.7 164.2
(0,1) (0,2) (0,3) (0,4)
(1,2) (1,3) (1,4)
(2,3) (2,4)
(3,4)
183.7 178.1 173.2 168.5 164.2
 

10このデータを使ったプログラムに変更する時でも#defineを書き換えるだけですむ。

 D.3 #define による変数型定義

 Cプログラム中で,例えばunsigned charという型名を毎回書かされるのはいやだということがある。
この様な場合には
#define UCHAR unsigned char
と定義しておけば
UCHAR c1,c2,c3;
のように変数定義に使えるようになる。

 D.4 #define によるマクロ

 Cプログラム中で,短い作業を関数化せずに#defineを使ってマクロ命令を作ることがある。しかし,#defineによるマクロ命令は後で述べるように危険な副作用があるので,引数を伴う使用はやめるべきである。

これは使っても安全なマクロ命令
List D.4.1 PRINTDATA()を関数のように見せているが実はマクロ命令
main中に展開されてからコンパイルされる。
あまりご利益はないので,すなおに関数にしたほうがよい。

#include <stdio.h>

#define SIZE 5
#define PRINTDATA() {int i;for (i=0;i<SIZE;i++) {printf("%.1f ",height[i]);} printf("\n");}

int main()
{
    double height[SIZE]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    PRINTDATA();
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<SIZE-1;i++) {
        for (j=i+1;j<SIZE;j++) {
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
        PRINTDATA();
    }
    /*表示部*/
    PRINTDATA();
    return 0;
}


173.2 168.5 178.1 183.7 164.2
183.7 168.5 173.2 178.1 164.2
183.7 178.1 168.5 173.2 164.2
183.7 178.1 173.2 168.5 164.2
183.7 178.1 173.2 168.5 164.2
183.7 178.1 173.2 168.5 164.2
 


使うと危険なマクロ(一見便利そうだが,危険)。引数をとることが出来,便利な関数のように見える。
List D.4.2 2乗を計算するSQUARE()関数をマクロで作る。
      実行結果をみると,うまくいっているように見える。

#include <stdio.h>

#define SQUARE(x) x*x

int main()
{
    double x,y;
    x=4.0;
    y=SQUARE(x);
    printf("x=%f y=%f\n",x,y);
    return 0;
}


x=4.000000 y=16.000000
 

ここまではよいが次の例を見てみよう。

List D.4.3 2乗を計算するSQUARE()関数をマクロで作る。
      yは50になるはずでは?

#include <stdio.h>

#define SQUARE(x) x*x

int main()
{
    double x,y;
    x=4.0;
    y=SQUARE(x+1)*2;
    printf("x=%f y=%f\n",x,y);
    return 0;
}


x=4.000000 y=10.000000
 

実は
    y=SQUARE(x+1)*2;
は,単純な文字の置き換えなので
    y=x+1*x+1*2;
になってしまい,50ではなく
4+4*1+1*2=10
になってしまった。


それでは次のようにやってみよう。
List D.4.4 2乗を計算するSQUARE()関数をマクロで作る。
      これで解決したかも?

#include <stdio.h>

#define SQUARE(x) ((x)*(x))

int main()
{
    double x,y;
    x=4.0;
    y=SQUARE(x+1)*2;
    printf("x=%f y=%f\n",x,y);
    return 0;
}


x=4.000000 y=50.000000
 

#define SQUARE(x) ((x)*(x))を使えば
    y=SQUARE(x+1)*2;

単純な文字の置き換えなので
    y=((x+1)*(x+1))*2;
になるので,成功する。


これで大丈夫と思ったら,どうにもならない危険がある。

List D.4.5 2乗を計算するSQUARE()関数をマクロで作る。

#include <stdio.h>

#define SQUARE(x) ((x)*(x))

int main()
{
    double x,y;
    x=4.0;
    y=SQUARE(x++);
    printf("x=%f y=%f\n",x,y);
    return 0;
}


x=6.000000 y=16.000000
 

x=5 y=16を期待していたが,x=6になってしまった
    y=SQUARE(x++);
の単純な文字の置き換えになるので
    y=((x++)*(x++));
になってしまう。
yは期待したとおりだが,
xは2回インクリメントされるので,期待とは違ってしまった。

#defineを用いたマクロ命令は,このように危険があるため,使用は勧められないが,
他人が作成したプログラムを読む時には必要なので解説した。
使うときは,文字がそのまま展開されることに注意が必要である。

 D.5 #if による条件コンパイル

 プログラムのデバッグの時にはある部分をコンソールに出力し,デバッグが終わったらその部分は出力したくないといった場合に,条件に応じてコンパイルすると便利である。

List D.5.1 ソート部分をデバッグするときにはその部分を表示させたい

            #ifdef ONDEBUG
            printf("(%d,%d) ",i,j);
            #endif


      というのは

      ONDEBUG が定義されていたら
      すなわち「#define ONDEBUG」が上部に記述されていたら

            printf("(%d,%d) ",i,j);

      をコンパイル時に有効にしなさいの意味

#include <stdio.h>

#define SIZE 5
#define ONDEBUG

int main()
{
    double height[SIZE]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<SIZE;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<SIZE-1;i++) {
        for (j=i+1;j<SIZE;j++) {
            #ifdef ONDEBUG
            printf("(%d,%d) ",i,j);
            #endif
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
        #ifdef ONDEBUG
        printf("\n");
        #endif
    }
    /*表示部*/
    for (i=0;i<SIZE;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}


173.2 168.5 178.1 183.7 164.2
(0,1) (0,2) (0,3) (0,4)
(1,2) (1,3) (1,4)
(2,3) (2,4)
(3,4)
183.7 178.1 173.2 168.5 164.2
 

List D.4.2 「List D.4.1 」がプリプロセッサ作業後には
      次のようになる

      :
ここにはstdio.hが展開されている
      :

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };
    double tmp;
    int i,j;
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    for (i=0;i<4;i++) {
        for (j=i+1;j<5;j++) {
             printf("(%d,%d) ",i,j);
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
        printf("\n");
    }
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}


注意 「#define ONDEBUG」はONDEBUGを「何もないもの」に定義したという意味。
    ONDEBUGが定義されたかどうかが重要でどのような値に定義されたかは重要ではない。

次に
#define ONDEBUG
を無効にしてコンパイルしてみよう

List D.5.3 ソート部分のデバッグが終わったので,その部分を表示させたくない

      #define ONDEBUG 
            をコメントアウト(コメントにして,無効にする)

#include <stdio.h>

#define SIZE 5
/*#define ONDEBUG*/

int main()
{
    double height[SIZE]={
        173.2,168.5,178.1,183.7,164.2
    };/*cm*/
    double tmp;
    int i,j;
    /*表示部*/
    for (i=0;i<SIZE;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    /*ソート(大きい順に並び替え)部*/
    for (i=0;i<SIZE-1;i++) {
        for (j=i+1;j<SIZE;j++) {
            #ifdef ONDEBUG
            printf("(%d,%d) ",i,j);
            #endif
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
        #ifdef ONDEBUG
        printf("\n");
        #endif
    }
    /*表示部*/
    for (i=0;i<SIZE;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}


173.2 168.5 178.1 183.7 164.2
183.7 178.1 173.2 168.5 164.2
 

List D.4.4 「List D.4.3 」がプリプロセッサ作業後には
      次のようになる

      :
ここにはstdio.hが展開されている
      :

int main()
{
    double height[5]={
        173.2,168.5,178.1,183.7,164.2
    };
    double tmp;
    int i,j;
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    for (i=0;i<4;i++) {
        for (j=i+1;j<5;j++) {
            if (height[i]<height[j]) {
                tmp=height[i];
                height[i]=height[j];
                height[j]=tmp;
            }
        }
    }
    for (i=0;i<5;i++) {
        printf("%.1f ",height[i]);
    }
    printf("\n");
    return 0;
}


このように#define ONDEBUGの有無でコンパイル直前にソースプログラムを書き換えたのと同様の効果を得ることが出来る。

なお
#define ONDEBUG
のところは
#define ONDEBUG 10
のように数値が定義されていてもよい。

このほかに

#define ABCD
  :

#ifdef ABCD      /*ABCDが定義されていたら*/
  〜
#else            /*そうでなかったら*/
  〜
#endif

#define ABCD
  :

#ifndef ABCD      /*ABCDが定義されていなかったら*/
  〜
#else            /*そうでなかったら(定義されていたら)*/
  〜
#endif

#define ABCD 100
  :

#if ABCD==100    /*ABCDが100に定義されていたら*/
  〜
#else            /*そうでなかったら*/
  〜
#endif

#define ABCD
  :

#if defined(ABCD)      /*ABCDが定義されていたら*/
  〜
#else            /*そうでなかったら*/
  〜
#endif


#define ABCD
#define EFGH
  :

#if defined(ABCD) && defined(EFGH)     /*ABCDとEFGHの両方が定義されていたら*/
  〜
#else            /*そうでなかったら*/
  〜
#endif


#define ABCD
#define EFGH
  :

#if defined(ABCD) || defined(EFGH)     /*ABCDまたはEFGHが定義されていたら*/
  〜
#else            /*そうでなかったら*/
  〜
#endif

のような条件コンパイルも出来る。

定義の抹消

#define ABCD
  :

#undef ABCD   /*ABCDの定義抹消*/
  :

#define ABCD 10
  :

#ifdef ABCD
#undef ABCD     /*ABCDの定義抹消*/
#define ABCD 20 /*ABCDを再定義*/
#endif 
  :

のように使うことも出来る。

二重定義はコンパイルエラーになるのでこの方法で回避策をとることもできる。

πがどこかで3.14などと定義されているかもしれない。
桁数を増やして定義しなおしたい場合は次のように行なう

#include ・・・・

#ifdef PI
#undef PI     /*PIの定義抹消*/
#define PI 3.141592653589793 /*PIを再定義*/
#endif
  :