C言語入門講座

トップ>コンテンツ>C言語>9.プリプロセッサとメイクファイル

 当講座はメールマガジン「Cプロダクション」の2000年5月10日から2002年7月31日までの記事を再編集したものです。

9.プリプロセッサとメイクファイル

9-1. 種類
9-2. ファイルのインクルード
9-3. ヘッダーファイル
9-4. 分割コンパイル
9-5. ヘッダーとリンク
9-6. メイクの利用
9-7. メイクファイルのマクロ
9-8. マクロ展開
9-9. 条件付コンパイル
9-10. デバッグモードの追加

9-1. 種類

プリプロセッサの種類は以下8種類あります。

#include
#define
#if
#else
#elif
#endif
#ifdef
#ifndef

#includeについては前に少しだけ説明しましたが説明不足ですね(^^;
しかも分割コンパイルの仕方も書いてない。それも含めてコンパイラの働きか
ら説明する必要がありそうです。

それでも高度な開発環境でプログラムを書いている人には分割コンパイルや
makeについて特に気にしなくてもいいので説明すべきかどうか...

gcc や lcc などコンソール型のコンパイラを利用する方には比較的重要なので
簡単に説明しようと思います。

9-2. ファイルのインクルード

 いままでは、この #include<stdio.h> は printf() などを使用するために
必要なので記述してください。とだけ説明していました。

今回は、#include について詳しく解説します。

まず、# の記号。
この記号があるとその行はプリプロセッサ行として扱われます。
注意しないといけないのは、効力がその行限りと言う事です。
つまり、

#        include      < stdio.h >

は良くても、(LSI-CとVC++で確認)

# include
<stdio.h>
や、

#include
#<stdio.h>
はダメと言う事です。

そして、このプロプロセッサはコンパイラに引き渡される前にソースファイル
に対してマクロ処理やファイルの取りこみそしてコメント文の除去を行います。

例えば、
/* C-Production と表示 */
#include<stdio.h>
main()
{
    printf("C-Production\n");
}

というプログラムがあるとします。
これをコンパイルすると、最初に前処理としてコメント文が抹消され
プリプロセッサによる処理が行われます。

include 疑似命令は指定したファイルをコンパイル時に挿入します。
上の場合だと、コンパイル時の状態は、

------------stdio.hの部分------------
              .
              .
int printf(const char *format, ...);
              .
              .
-------------------------------------
main()
{
    printf("C-Production\n");
}

のようになっています。

あと、もうひとつ例を
┌main.c─────────┐┌file──────────┐
│main()         ││            │
│{            ││int x=1,y=2,z;     │
│#include"file"     ││z=x+y;         │
│}            ││            │
└────────────┘└────────────┘
とします。この場合もコンパイルする時は、

main()
{

int x=1.y=2.z;
z=x+y;

}

となります。つまりincludeは一緒にコンパイルするファイルを取りこむ時に
使われます。(翻訳単位が同じという意味)

◇ファイル指定の "..." と <...> の違い。

今日の最後は、ファイル指定につかう括弧の違いについてです。
この2つの違いは取りこむファイルを探す場所の順番にあります。

<...>の場合はあらかじめコンパイラ等の設定ファイルに定義されている
ライブラリの場所から検索し無ければソースファイルのカレントディレクトリ
を検索します。

次に"..."の場合はソースファイルのカレントディレクトリから検索し無ければ
ライブラリから検索します。(LSI−Cではライブラリ検索しません)

これによって、ライブラリとカレントディレクトリに同名のファイルがある時
どちらを取りこむかを指定することができます。

自作(拡張)ライブラリを取りこむ時は"..."、
標準ライブラリから取りこむ時は<...>を使うのが普通になります。

9-3. ヘッダーファイル

◇ヘッダーファイルとは?

その名の通り頭になるファイルです。(ぉぃぉぃ)
具体的には、関数のプロトタイプ宣言や構造体・共用体の定義などを書きこみ
ます。
これは、特にメインのソースファイルに書いても問題は無いのですが、
ヘッダーファイルに分割する事で次のような利点があります。

1.宣言部と本体を分けることによってプログラムが見やすくなる。
  また、使われている関数を調べるのが楽になる。

2.分割コンパイルする場合、extern宣言の代わりにヘッダーファイルを
  インクルードする事で作業を楽にできる。

3.作ったプログラムを再利用しやすくする。
  コンパイル済みのライブラリであってもヘッダーファイルがあることで
  他のプログラムから利用できるようになる。

の3点です。とくに3番目は企業にとっては内部設計を非公開のまま
外部からの利用を許可できるので大きな利点です。
逆に、UNIX系OSになるとフリーウェアがソースで公開され、バグがあれば
作者に限らず直してくれる(改変も許可)のと、OSに沢山のディストリビュー
ションがあるため、互換性のためにもコンパイルして利用してもらうという点
で3番より2番目の利点のほうが大きくなります。

また、必要性としては大きなプロジェクト等、多くのプログラムか1つの大き
なアプリケーションを作る時などは、上の理由から絶対必要になります。


◇ヘッダーファイルの作り方

それでは、ヘッダーファイルを作ってみましょう。

1.まず、ヘッダーファイルに書きこむべき内容を調べます。

  extern宣言
  関数のプロトタイプ宣言
  構造体・共用体の定義
  インライン関数の宣言・定義
  マクロ展開の定義
  ・・・など

※この内容はC++の規則を元に書いています、Cではヘッダーについて
あまり厳密ではありませんが現在のコンパイラーの多くがC++なので
あえて厳密にしています。またC++に移行した時にやりなおすことも
ないのでそうしています。

ということで、次にヘッダーに書くことをオススメできないもの。

  関数の定義(本体)
  変数(オブジェクト)の宣言
  その他ソースに書くべきもの
  ・・・です。

2.次にヘッダーファイルの拡張子を(.h)にしてなるべくソースと同じディレ
  クトリにいれる。

3.最後に呼出し側のソースからはファイルの先頭付近でインクルードする。

では実際に以下のプログラムをヘッダーとソースの2つに分けます。

/* サンプルプログラム */
#include<stdio.h>
#define PRINT printf
int fadd(int, int);
int x;

main()
{
  int a=2,b=3,c;
  c=fadd(a,b);
  PRINT("c = %d\nx = %d\n",c,x);
}

fadd(int i, int j)
{
  x = i + j;
  return x;
}

/* End Of File */

ここで、ヘッダーに書きこむ対象になるのは
#include<stdio.h>〜int fadd(int, int);までです。
int x;をヘッダーにいれるのは好ましくありません。
(オブジェクト宣言は最終的に機械語に翻訳される内容だから)

そして2つに分けた後は以下のようになります。

●ヘッダー(header.h)
/* header.h */
#include<stdio.h>
#define PRINT printf
int fadd(int, int);
/* End Of File */

●ソース(source.c)
/* source.c */
#include"header.h"
int x;

main()
{
  int a=2,b=3,c;
  c=fadd(a,b);
  PRINT("c = %d\nx = %d\n",c,x);
}

fadd(int i, int j)
{
  x = i + j;
  return x;
}

/* End Of File */

となります。

9-4. 分割コンパイル

◆分割コンパイル

まず最初にソースファイルをコンパイルしてからアプリケーションができるま
での流れを図に示します。

◇実行ファイルができるまでの流れ

ソースファイル
   │
   │←プリコンパイル(マクロ等の処理)
   │←コンパイル(機械語に変換)
   ↓
オブジェクトファイル
   │
   │←リンク(機械語ファイルを結合)
   ↓
アプリケーション

これは基本的な流れです。開発環境によって多少の違いはあります。
因みに、lcc や gcc コンパイラでは標準でコンパイルとリンクを実行するので
簡単なプログラムではあまりリンクを意識する事は無いと思います。
また、VC++ではリンクのことをビルドと言います。
(実際の動作は make そのものですが・・・)

これでは、次のプログラムを例にしてみます。

#include<stdio.h>
main()
{
  printf("C-Production\n");
}

そして、コンパイラのコマンドに -c を付けます。(コンパイルのみ実行)
例:lcc -c main.c

すると、まずプリコンパイルでソールファイルに stdio.h の内容が
挿入されます。その状態がコンパイルの対象になります。
そして、コンパイルが終了すると機械語のオブジェクトファイルが
生成されます。(.o または.obj)

ここでは、まだ実行することができません。stdio.hをインクルードして
printf()が利用できるようになっても、printf()本体はstdio.hに含まれて
ません。次に、ライブラリとリンクすることによってprintf()が実装され、
アプリケーションが完成します。

例 lcc main.obj

これが実際の流れになります。
ここではまだ分割コンパイルについては説明していません。

#include で取りこんだファイルはコンパイルの前に挿入され一つのファイルに
なるので、コンパイルは分割でなく一括です。

それでは最後に、分割コンパイルでアプリケーションを作成してみます。

次の3つのファイルから構成して。
file1にmain()
file2に別の関数(今回はfadd())
file2.hにはfile2で定義する関数の宣言
をそれぞれ書きます。
-----------------------------
/* file1.c */
#include<stdio.h>
#include"file2.h"

main()
{
  int a=2,b=3,c;
  c=fadd(a,b);
  printf("c = %d\n",c);
}
/* End Of File */

-----------------------------
/* file2.h */
int fadd(int, int);
/* End Of File */

-----------------------------
/* file2.c */
#include "file2.h"
int fadd(int a,int b)
{
	int x;
	x = a + b;
	return x;
}
/* End Of File */

そしてコンパイル時には(例:LSI−C)
> lcc -c file1.c
> lcc -c file2.c

でそれぞれ、file1.obj file2.obj が作成されます。
そして、2つのオブジェクトファイルをリンクして完成させます。

lcc file1.obj file2.obj

これでfile1.exeという名のアプリケーションができます。
(UNIX系のコンパイラではa.out)

特に名前を指定したい時は、

lcc file.exe file1.obj file2.obj

のようにしてください。

9-5. ヘッダーとリンク

分割コンパイルとヘッダーファイル関係を説明します。

今度はソースファイルをもう一つ増やして、file3.cそのヘッダーのfile3.hを
追加します。そして依存関係は次のようになります。

   ┌────┐┌────┐
   │file2.h ││file3.h │
   └────┘└────┘
    /  \  /  \
┌────┐┌────┐┌────┐
│file1.c ││file2.c ││file3.c │
└────┘└────┘└────┘

つまり、filr3.c で定義した関数はfile2.cで利用ということにします。
まずは3つのソースファイルをコンパイルします。

>lcc -c file1.c file2.c file3.c

これで3つオブジェクトファイル

file1.obj file2.obj file3.obj

が生成されます。(gcc の方もそれぞれ読み替えてください)
それで、実行ファイルを作る時

>lcc file1.obj file2.obj file3.obj

とすれば file1.exe ができます。(gcc では a.out)
そしてこの時、file1.c を修正したとします。
その時は、file1.c だけ再コンパイルすれば済みます。

>lcc -c file1.c
>lcc file1.obj file2.obj file3.obj

もちろん lcc はリンカも兼ねているので。

>lcc file1.c file2.obj file3.obj

としてもうまく行きます。
file2.c file3.c についても変更したら変更したファイルのみ再コンパイルするだけで済みます。

これこそ分割コンパイルの利点になります。
まず、修正があっても部分的に再コンパイルすればいいので
コンパイルにかかる時間が最小限で済みます。

そしてヘッダーファイルを作っているとコンパイル済みのライブラリーの場合、
利用したい時にヘッダーファイルをインクルードすればよいことです。
そうすれば、ライブラリーがコンパイル済みでソース内容がわからなくても
ヘッダーファイルによりどんな関数があるのか、どのように呼び出すのかが
分かります。

次に、ヘッダーファイルを書き換えた場合はどうなるでしょう。
この場合、そのヘッダーファイルをインクルードしているファイル全てを
再コンパイルしなくてはなりません。
(ソースに挿入されるわけですから当然ですね)
file2.h を変更した時は file1.c と file2.c
file3.h を変更した時は file2.c と file3.c
を再コンパイルしなければなりません。

こうやって、ソースファイルを分担する事により、複数のプログラマーで
開発したり、再利用したりしやすくなります。

ただ、このまま、ソースファイルが100個、200個になるとリンクするとき

>lcc file1.obj file2.obj file3.obj file4.obj file5.obj file6.obj ...

とてもじゃないけどやってられないです。
ハッキリ言って改変するたびこんなコマンドを打つのは面倒な上に
タイプミスによるエラー続発の温床になります。

こんなことならコンパイル・リンクのための
バッチファイルがあればいいのに・・・

実は make という開発支援するためのプログラムが存在します。
それは次回で・・・

9-6. メイクの利用

ソースファイルが100個、200個になると

>lcc file1.obj file2.obj file3.obj file4.obj file5.obj file6.obj ...

このようにコマンドを打つことになり、
タイプミスによるエラー続発の温床になります。
と説明しました。

こんなことならコンパイル・リンクのための
バッチファイルがあればいいのに・・・ということで今回は
コンパイル・リンクを支援する make を紹介します。

Linux でソースで配布されているアプリケーションをインストールする時
必ずといっていいほど make を利用します。
・・・それは、もうコンパイルのコマンドが make ?って思うほどです。

それで今日は make って何? という部分と使いかたについて説明します。

なお前回のファイル構成(依存関係)のままで説明します。
   ┌────┐┌────┐
   │file2.h ││file3.h │
   └────┘└────┘
    /  \  /  \
┌────┐┌────┐┌────┐
│file1.c ││file2.c ││file3.c │
└────┘└────┘└────┘
前回、一度コンパイルした後ソースファイルの改変など行った後
どのファイルだけをコンパイルすればよいか説明しました。
しかし、ファイル数が膨大になると状況の把握が困難になり、
コンパイルのし忘れが原因で実行ファイルを作成した後、更新した部分が
適用されていなかったり、ヘッダーファイルの改変となるともっと管理しづらく
最悪の場合リンクエラーが発生しチェックが大変になります。
(実際、リンクエラーはコンパイルエラーよりも対処が大変)
そんなことでは分割コンパイルの利点も無くなってしまいます。
そこで、make コマンドを利用する事によりファイルの管理を自動化させる
ことができます。ただしバージョン管理まではしません。

※バージョン管理を支援するソフトとして RCS,CVS 等があります。
 コマンド名はci(check in), co(check out) です。
 ただしUNIX系での話です。

では、早速使い方

>make

で終わりです。後は特に指定が無い限り何もいりません。
これだけで、コンパイルが必要なファイルをコンパイルして更新し
アプリケーションを完成させます。
とにかく、ソースを書き換えたら make。ラクでしょ。
これならコンパイルのし忘れやファイルの更新状態について考えなくてもいい
わけです。
そしてこの make にルールを定義するために makefile を作る必要があります。
上の make とだけコマンド打っていますが、このとき makefile を参照し
そこに書かれたルールにしたがってコンパイル・リンクしているのです。
この makefile は別のファイル名でもかまいません。
そのときはコマンドラインで指定する必要があります。
makefile の代わりに install というファイルをルールとして利用するとします。
コマンドラインは

>make -f install

となります。makefile 以外のファイルを指定する時はオプション -f
を使用してください。

これに近い記述、Linuxを触れている人なら見たことがあると思います。
ソースをコンパイルするだけでなくアプリケーションを生成する場所もルール
として指定すればインストーラーとしても使えたりします。

注:実際にUNIX等でよく使う make install のコマンドラインは
install というメイクファイル名でなく、 makefile 中で定義されて
いる install ターゲットの可能性があります。

ということで最初に出てきた5つのファイルもmakefileにルールを記述する
だけで、あとは make コマンドによる更新をすることにより煩わしさを
解消する事ができます。


◇メイクファイルの書き方

make につかうメイクファイルの書き方について説明します。
※LSI−C以外のコンパイラの方はそれぞれコマンド行を置き換えてください

プログラムの構成は前回とと同じです。
   ┌────┐┌────┐
   │file2.h ││file3.h │
   └────┘└────┘
    /  \  /  \
┌────┐┌────┐┌────┐
│file1.c ││file2.c ││file3.c │
└────┘└────┘└────┘

それでは、早速ルールの定義といきましょう。

ルールの定義は上から順番に最終目標、次にその直前の目標という感じで
定義していきます。

最終目標
その直前の目標
またその直前の目標
   ・
   ・
   ・

上のプログラム構成を例にしてまず、最終的に program.exe という実行
ファイルを作るとします。

そのとき、まずメイクファイルには

program.exe : 

と書きます。そのまま引き続き右側に program.exe を作るときに必要な
ファイルを定義します。今回はオブジェクトファイルからリンクして
実行ファイルを作るので file1.obj file2.obj file3.obj が構成する
ファイルです。

program.exe : file1.obj file2.obj file3.obj

よって最初の記述はこのようになります。

書式:(必ずコロンの両脇は半角スペースを入れてください)
ターゲット : 依存ファイル

次に行にはターゲット program.exe のコマンドラインを書きます。

    lcc -o program.exe file1.obj file2.obj file3.obj

先頭の空白はここではスペースにしていますが実際に書く時は必ずタブを
入れて下さい。(ターゲット行との区別のため)

次にfile1.obj file2.obj file3.objを作るため定義をします。先の例と同じく

file1.obj : file1.c file2.h
    lcc -c file1.c

file2.obj : file2.c file2.h file3.h
    lcc -c file2.c

file3.obj : file3.c file3.h
    lcc -c file3.c

と書きます。

これで、メイクファイルは完成です。
分割コンパイルも、更新も make 一発で完了します。

9-7. メイクファイルのマクロ

前回、書いたメイクファイル(LSI−C用)をBCCやGCCでコンパイル
しようとするとかなり変更箇所があります。

そこで、変更箇所を最小限にできるように同じような記述の部分を省略できる
部分は省略し、マクロを定義する事によって一括変更できるようにします。

プログラムの構成:
   ┌────┐┌────┐
   │file2.h ││file3.h │
   └────┘└────┘
    /  \  /  \
┌────┐┌────┐┌────┐
│file1.c ││file2.c ││file3.c │
└────┘└────┘└────┘

メイクファイル:
file.exe : file1.obj file2.obj file3.obj
	lcc -o file.exe file1.obj file2.obj file3.obj

file1.obj : file1.c file2.h
    lcc -c file1.c

file2.obj : file2.c file2.h file3.h
    lcc -c file2.c

file3.obj : file3.c file3.h
    lcc -c file3.c

まずこのメイクファイルの中でソースファイルとオブジェクトファイルの
名前が同じ時(拡張子は省く)はコンパイルの定義しなくてもソースをを探し当
てコンパイルしてくれます。

file1.obj を作る定義が無いときは file1.c を探してコンパイルします。

そのかわり、ヘッダーとの依存関係だけ定義する必要があります。
(さすがに、そこまでは理解できないから)

file1.obj : file2.h

file2.obj : file2.h file3.h

file3.obj : file3.h

また依存関係がまったく同じならさらにまとめる事ができます。

file1.obj : header.h

file2.obj : header.h

の場合、

file1.obj file2.obj : header.h

とできる。

次に、マクロ定義。
マクロ定義を使えば何度も記述する部分を短縮し、変更があった時も定義だけ
更新すればよいのでメイクファイルの管理が簡単になります。

まず、環境によって違うのはコンパイラ。ということでコンパイラをマクロに
定義します。

CC = lcc

そして、コマンド行は
        $(CC) -o file.exe file1.obj file2.obj file3.obj

となります。違うコンパイラを使う時は cc = の右側だけ変更すれば済みます。

そして今度はオブジェクトファイル

OBJECT = file1.obj file2.obj file3.obj

とすれば、

file.exe : $(OBJECT)
        $(CC) -o file.exe $(OBJECT)

になります。あと定義するとしたら実行ファイルの名前。

DOS/Windows では拡張子に exe を付けますが他のOSでは exe を付ける事は
まずありません。よってココも定義する価値があります。

TARGET = file.exe

$(TARGET) : $(OBJECT)
        $(CC) -o $(TARGET) $(OBJECT)

※おまけ
  # は行末までのコメント記号として使えます。
 ただし、コマンドラインの行とマクロ定義の行では利用できません。

よって最終的にメイクファイルは
# Makefile
CC = lcc
TARGET = file.exe
OBJECT = file1.obj file2.obj file3.obj

# コメントです。
$(TARGET) : $(OBJECT) # 下のエコーで make が動作している事を表示
        @echo "*** Making $(TARGET) ***"
        $(CC) -o $(TARGET) $(OBJECT)

file1.obj : file2.h
file2.obj : file2.h file3.h
file3.obj : file3.h

となりました。

make についてはまだまだたくさんあります。一種のスクリプト言語のような
深いものなのでとてもではないですが私の能力ではカバーしきれないところが
あります。(普段VC++を利用していて make を使う事が無いのも一理あり)

また、C言語の学習ならばこの程度でも大丈夫という妥協も入っています。

          

9-8. マクロ展開

今日からは少しもどってプリプロセッサの続きです。
 前回まで #include に始まり、それから分割コンパイル・make と進んで
参りました。
今回からは #define の解説です。
#define は定義した文字の変換をします。

例えば

#define PRINT printf

と定義します。
すると、ソースプログラム中の PRINT はすべて printf に変換されます。

#include<stdio.h>
#define PRINT printf

main()
{
    PRINT("It's macro.\n");
}

ちゃんと PRINT が printf として使えたと思います。
次に、値に対して文字を割り当ててみます。

#define TRUE 1
#define FALSE 0

もっともこれらの定義は stdio.h 等にしてある可能性はありますが
念のため定義しておきます。
そうするとプログラム中の処理で成功・失敗を処理する時の表現が
分かりやすいものになります。

if(check==FALSE){ /* この使い方は少ない、if(!check)の方がメジャー */
    printf("処理失敗、プログラムを強制終了します。\n");
    return FALSE ;
    }

あとTRUEの場合などは

while(TRUE) /* while(1) に同じ...無限ループ */

のような使い方があります。
さらに、#define では引数を入れることにより関数式を定義することも
できます。

例:#define DOUBLE(x) x+x 

ソース中に DOUBLE(5) と書くと、5+5 に変換されます。

注意!
あくまで #define は文字の変換です。

#include<stdio.h>
#define DOUBLE(x) x + x 

main()
{
    int x;
    x = DOUBLE(4) * DOUBLE(6);
    printf("%d\n",x);
}

とプログラムを組んだ場合、4 + 4 * 6 + 6 となり、優先順位が変わって
しまって x は34になってしまいます。
これでは関数としての働きがありません。
そうならないためにも、優先順位が変わらないように(括弧)で囲んでください

#define DOUBLE(x) x + x   /* 悪い例 */
#define DOUBLE(x) (x + x) /* これでも、数式が引数として渡されたら危険 */
#define DOUBLE(x) ((x) + (x)) /* よい例 */

2番目の数式が渡せれたら危険という例を下に示します。

#include<stdio.h>
#define square(x) (x * x) 

main()
{
    int x;
    x = square(3+4);
    printf("%d\n",x);
}

答えは49ではなく19になってしまいます。
これもsquare(3+4)が、(3+4*3+4)となるためです。
これを

#define square(x) ((x) * (x))

と定義すればsquare(3+4)は、ちゃんと((3+4)*(3+4))になります。


※最後に型を定義する時の注意
#defineを使えば、const unsigned long int のような長いものも単純に
できますが、typedef という型の定義をする予約語ああリます。

こちらのほうが安全性が高いので型の定義についてはtypedefを
ご利用ください。
(...まだ説明してませんでした、構造体の時にでも解説します)

9-9. 条件付コンパイル

条件付コンパイルの紹介です。

#if
#else
#elif
#endif

今日はこの4つの紹介です。

#if 定数式

これは、定数式の真・偽によってコンパイルするかどうかの判定をします。
真であれば以下のプログラムをコンパイルします。

#else

こちらは、#ifや#elifの定数式が偽であった時にコンパイルする内容を以下に
書きます

#elif

#elifはC言語の else if の意で#ifで偽であった時次に#elifで真・偽を判定
します。そこで真ならばこれ以下のプログラムをコンパイルします。

#endif

#ifの範囲の終わりを示します。


書式1:

#if 定数式
  真の時コンパイルされる内容

#endif


書式2:

#if 定数式
  真の時コンパイルされる内容

#else
  偽の時コンパイルされる内容

#endif

書式3:

#if 定数式1
   定数式1が真の時コンパイルされる内容

#elif 定数式2
   定数式2が真の時コンパイルされる内容

#else
   全て偽の時コンパイルされる内容

#endif

となります。
因みに、定数式の部分は#defineで定義された識別子を利用する事ができます。


9-10. デバッグモードの追加

残りのプリプロセッサ
#ifdef
#ifndef
の解説をして実際にプログラムにデバッグモードを追加します。

#ifdef 識別子

識別子が定義されていたら以下をコンパイル。

#ifndef 識別子

識別子が定義されていないなら以下をコンパイルです。
#if と違うのは定数式でなく識別子であることです。
識別子として宣言されたもの以外、具体的には数字の1などは
使えません。

そしてこれらは#ifの代わりに最初に書きます。
書式については前回と同じになります。

例:
#ifdef 識別子  (または#ifndef 識別子)

識別子が定義されている(いない)とコンパイルする。

#elif 定数式

上記がコンパイルされなかったとき定数式が真であればコンパイル。

#else

上記のどれもがコンパイルされなければここをコンパイル

#endif

となります。

それでは、最後にデバッグモードとしてプログラムの実行状態をトレースする
プログラムを書きます。

開発環境にはデバッグツール(デバッガ)が用意されていますが、説明書に
使い方が載っていなかったりで実際使わない方のほうが多いと思います。
また、ソースファイルそのものでトレースできるように加工すれば、開発環境
が変わったり、デバッガのないフリーコンパイラでも開発の手助けになります。

#include<stdio.h>
int fadd(int, int);

main()
{
  int i,j,k;
  for(i=0;i<3;i++){
    for(j=0;j<3;j++){
      k=fadd(i,j);
      printf("i + j = %d\n",k);
    }
  }
}

int fadd(int x, int y)
{
  return (x + y) ;
}

上のプログラムに関数実行と変数の状態のトレースを追加します。

#include<stdio.h>
#define ON  1
#define OFF 0
#define DEBUG    /* DEBUG mode 不可にする時はコメントアウト*/
#define TRACE ON /* OFFにすると変数のトレースを無効にします */

int fadd(int, int); /* 2つの値の和を求める関数 */

main()
{
  int i,j,k;

/* C言語ではオブジェクトの宣言を最初に書かないと
  エラーになるので#ifdefを次に書きました。 */

  #ifdef DEBUG /* メインの始まりを知らせる */
    printf("Start main function\n");
  #endif

  for(i=0;i<3;i++){
    for(j=0;j<3;j++){
      k=fadd(i,j);
      #if TRACE
        printf("i = %d, j = %d\n",i,j);
      #endif
      printf("i + j = %d\n",k);
    }
  }
  #ifdef DEBUG /* メインの終了を知らせる */
    printf("End main function\n");
  #endif
}

int fadd(int x, int y)
{
  #ifdef DEBUG /* faddが呼び出された事を通知 */
    printf("Call fadd function\n");
  #endif

  return (x + y) ;
}

今回は出力の量が多いのでファイルへ出力しましょう。
リダイレクトといいます。

filename > ans.txt

ウィンドウズをお使いの方でDOS窓を使うのが面倒な方はバッチファイルを
作ることでDOS窓を起動する手間が省けます。内容は上記そのままにして
拡張子は.batにしてください。

またLSI−CなどのDOSコンパイラを利用されている方はファイルの
実行時に.pifファイルが作られます。これを右クリックし出てきた
ダイアログから「プログラム」タブをクリックしその中の
「コマンドライン」の.exeの後に > res.txt と追加して次回からpifの
アイコンの方をダブルクリックしてみてください。


Copyright© 2000-2009 C-Production All Rights Reserved.