プリント基板設計・シミュレーション

TOP > アポロレポート > コラム > 第79回 プログラミングについて『いまさらですがC言語の門を叩こう その2 ~ プリプロセッサとヘッダーファイル ~』
コラム
2024/08/28

第79回 プログラミングについて『いまさらですがC言語の門を叩こう その2 ~ プリプロセッサとヘッダーファイル ~』

アポロレポート

 C言語には、コンパイラに対する命令とそうでないものがあります。コンパイラに対する命令とは、コンパイラがソースプログラムをコンパイルするときにだけ実行され、コンパイル後に作成されたプログラムには何も影響を与えないものです。このような命令をプリプロセッサと呼びます。例えば、C言語のプログラムで使用する #include というプリプロセッサ命令は、コンパイル時に指定したファイルを読み込んで下さいとコンパイラに命令するものです。

#include <stdio.h>

この様に記述すると、コンパイラに対して、ディフォルトのインクルード用のディレクトリにある stdio.h というファイルを読み込んで下さいということを意味しています。
この例ではファイル名が < > で囲まれているのでこのような解釈になるのですが、必ずしもいつもディフォルトのインクルード用のディレクトリから読み込むとは限りません。
もし、カレントのディレクトリにある abc.h というファイルを読み込もうとすれば、

#include "abc.h"

と " " で囲んでファイル名を指定します。

プリプロセッサ命令は # 記号から始まり、ソースプログラムに記述するときには行の先頭(1カラム目)から書くことが基本になっています。コンパイラによってはこの制限がないものもありますが、出来る限り守って下さい。またプリプロセッサ命令は1行で書かなければならないので、どうしても1行に納まらないときには次のようにします。

#include \
 "abc.h"

このようにバックスラッシュ(JISでは \)を行末に付けることで次の行に継続することができます。UNIXなどでもコマンドが長いときにはバックスラッシュで次の行に継続することができます。この様にUNIXのコマンドの入力の仕方とC言語とに共通点があるのはその生い立ちに密接な関係があるからなのです。

 この他にプリプロセッサ命令で必ずといっていいくらいに使われるのが、#define 命令です。この命令は定数やマクロを定義するものです。

#define ABC 10

この様に記述すると、コンパイラは ABC という単語を 10 に置き換えます。例えば、

#include <stdio.h>
#define ABC 10
main()
{
 printf("ABC=%d\n",ABC);
}

とすると、"..." 内の文字列の中の ABC は置き換えられませんが、コンマの後の ABCは 10 に置き換えられ、結果として ABC=10 と表示されます。意味を持った定数をこの様に定数名に定義することはプログラムを作成するときには非常に重要な意味があります。単純に半分にするとか1を加えるとかいった場合には定数をそのまま記述した方がプログラムが読みやすくなりますが、逆に円周率などをそのままの値で記述してしまったのでは返って意味が分かりづらくなってしまいます。
 また、多くのファイルで共通に使う値も、たとえその値が1とか2とかいう単純なものであっても定数名を定義した方がプログラムの保守がしやすくなります。さらにこういう場合は、ヘッダーファイル内に定数の定義をして、その値を使用するファイルがインクルードするようにすれば、定義の重複による保守性の低下を避けることができるようになります。

 #define 命令を使うときには特殊な場合以外は行の終わりにセミコロン ; を付けないようにして下さい。例えば、

#include <stdio.h>
#define ABC 10;
main()
{
 printf("ABC=%d\n",ABC);
}

とするとコンパイラは、

#include <stdio.h>
#define ABC 10;
main()
{
 printf("ABC=%d\n",10;);
}

と置き換えてしまい文法的なエラーとなるからです。

 #define 命令は定数の定義以外に、マクロの定義を行なうことができます。

#define MUL(a,b) (a)*(b)

のように記述すると、定数の定義ではなくマクロの定義になります。例えば、

#include <stdio.h>
#define MUL(a,b) (a)*(b)
main()
{
 printf("ABC=%d\n",MUL(10,20));
}

というプログラムの場合、

#include <stdio.h>
#define ADD(a,b) (a)*(b)
main()
{
 printf("ABC=%d\n",(10)*(20));
}

と置き換えられ、結果として ABC=100 と表示されます。マクロを定義するときに注意しなければならないのは、

#define MUL(a,b) (a)*(b)

の MUL の内容の部分の a と b をカッコで囲むところです。もし囲んでいなかったとすると、

#include <stdio.h>
#define MUL(a,b) a*b
main()
{
 printf("ABC=%d\n",MUL(10+20,20+30));
}

というプログラムは、

#include <stdio.h>
#define MUL(a,b) a*b
main()
{
 printf("ABC=%d\n",10+20*20+30);
}

というように置き換えられ、全く違った結果になってしまうからです。

 定数名やマクロ名に大文字を使っているのには、特にC言語上での約束事がある訳ではありません。このようにしているのは単に定数名とかマクロとかを一見して見分けられるようにするプログラミング上での配慮だけです。しかしながら、C言語では伝統的にこのように大文字を使っていますので、この伝統に従うことをお勧めします。

 #define 命令で定義した定数を抹消するには #undef 命令 を使用します。

#undef ABC

と記述するだけのものです。

#include <stdio.h>
main()
{
#define ABC 10
 printf("ABC=%d\n",ABC);
#define ABC 20
 printf("ABC=%d\n",ABC);
}

というプログラムの場合、ABC が2度定義されています。コンパイラやエラー検出レベルにもよりますが、マクロが再定義されているという警告が発生するはずです。あくまでも警告なのでプログラム上は問題はありませんが、気持ちのいいものではないと思います。こういうときには、

#include <stdio.h>
main()
{
#define ABC 10
 printf("ABC=%d\n",ABC);
#undef ABC
#define ABC 20
 printf("ABC=%d\n",ABC);
}

というように再定義する前にそれまでの定義を抹消するようにします。

 もう1つ重要なプリプロセッサ命令に、条件によってコンパイラが違ったコンパイルを行なうようにする命令です。

#if OS=="DOS"
#define ABC 10
#endif

#if OS=="UNIX"
#define ABC 20
#endif

このように記述すると、OS という定数名が #define 命令で "DOS" と定義されているときには、定数 ABC には 10 が、"UNIX" と定義されているときには 20 が定義されます。K&RのC言語にはなかったものですが、ANSIのC言語には上のような記述を、

#if OS=="DOS"
#define ABC 10
#elif OS=="UNIX"
#define ABC 20
#endif

と記述できるようになりました。#if の後に defined を付けて 定数名やマクロ名を指定すると、それらが定義されているか否かでコンパイルを変えることができます。

#if defined ABC
#define DEF 10
#else
#define DEF 20
#endif

または、

#if ! defined ABC
#define DEF 20
#else
#define DEF 10
#endif

などと記述します。また #if defined は #ifdef と、 #if ! defined は #ifndef と同じ意味ですので、

#ifdef ABC
#define DEF 10
#else
#define DEF 20
#endif

または、

#ifndef ABC
#define DEF 20
#else
#define DEF 10
#endif

とスッキリ記述できます。多分この #ifdef と #ifndef もANSIのC言語で追加された命令だと思います。

 さて、ここまでプリプロセッサ命令について説明してきましたが、ヘッダーファイルとの関係を少しお話ししましょう。
 C言語でプログラムを書くときにはソースファイルを複数に分けて作成することが殆どですので、それらに共通な定数やマクロ、変数の宣言などをそれぞれのファイル内で行なうと定数やマクロなどの内容を変えるときには関連するすべてのファイル内容を変更しなければなりません。これでは保守するのが大変です。こういうときに共通のファイル(ヘッダーファイル)を作成しておき、そこに定数、マクロ、変数の宣言などを記述しておきます。そして、各ソースプログラムがそのヘッダーファイルをインクルードするように記述しておきます。こうすることによって、ヘッダーファイルの内容を変えて再コンパイルをするだけですべての内容が変更されることになります。
 このようにヘッダーファイルを使用することは保守性を向上させますので、プログラムを作成するときには面倒でも後々のことを考えると行わなければなりません。

 それではまた次回。


そのお悩み、
アポロ技研に話してみませんか?

アポロ技研でワンランク上の物創りへ!
そのお悩み、アポロ技研に話してみませんか?