Cプログラミング入門

第21回Appendix 2 プリプロセッサ関連の記述

関数とマクロ関数

C言語には、通常の関数のほかにマクロ関数(関数形式マクロ)と呼ばれる関数があります。マクロ関数は、おもにインクルードファイル内で#defineを使って定義されたもので、これはコンパイル時にプリプロセッサによって関数内部のコードに置き換えられます。したがって、マクロ関数は本当の関数ではなく、マクロ関数を記述した箇所でその関数の内容を直接実行することになります。

マクロ関数には関数呼び出しのオーバヘッドがなく、通常の関数よりも高速に実行できるという利点がありますが、その反面、マクロ関数を記述する箇所ごとに別々に関数のコードが展開されるため、結果として実行バイナリファイルのサイズが大きくなるという欠点があります。

マクロ関数と通常の関数は、その関数を呼び出す立場のプログラマにとっては区別がつかず、どちらも同じ関数として考えることができます※。OSやコンパイラの環境によっては、同じ関数が一方ではマクロ関数、一方では通常の関数として実装されている可能性もあります。

※ ただし、引数の評価が複数回行われるようなマクロ関数では、その副作用が問題になる場合があります。

この連載でも何度か使用したputchar()は、多くの環境ではマクロ関数としてstdio.h内で定義されているでしょう。マクロ関数の定義は少々複雑な式になります。

リストA2.1は、putchar()の定義の例です。なお、現在のLinux、FreeBSDなどのstdio.hでは、リストA2.1よりさらに複雑な定義になっています。

リストA2.1 マクロ関数putchar()の定義例
#define putc(x, p)      (--(p)->_cnt < 0 ? \
                        _flsbuf((x), (p)) : \
                        (int) (*(p)->_ptr++ = (unsigned char) (x)))

#define putchar(x)      putc((x), stdout)

コメントの書き方

C言語でプログラムにコメント(注釈)を入れる場合は、

  /* comment */  

のように書きます。/*がコメントの始まりで、*/がコメントのおわりです。/**/の間に書かれた文字は、すべてコンパイラによって無視されます。コメントには、プログラムの内容を説明するための文章を書きます。

このほか、//を使うという、別の方式のコメントもあります。たとえば、

  printf("Hello World\n");  // Hello Worldを表示  

と書くと、//からその行の右端までがコメントとみなされます。

この「//」によるコメントは、本来はC++で導入されたものです。gccを含め、たいていのCコンパイラでは「//」方式のコメントも使えるようになっていますが、コンパイラによっては使えない場合もあります。⁠//」方式のコメントの使用は正式には避け、⁠/* */」方式のコメントを基本と考えた方がよいでしょう。

プログラムの一部をコメント扱いにして実行しないようにすることを、コメントアウトと言います。逆に、コメントアウトされていた部分のコメント記号を外して本来のプログラムに戻すことをアンコメントといいます。

プログラムのデバッグ時には、プログラム中の複数の行をまとめてコメントアウトしたいことがあります。コメントアウトしようとしているプログラムにすでに/* */によるコメントが記述された行が含まれている場合、 それらの行をまとめてコメントアウトすることはできません。これは、/* */によるコメントがネスティングできないという仕様によります。

このような場合は、

#if 0
   printf("Hello World\n");  /* すでにコメントが書いてある */
#endif

という方法を使うとよいでしょう。#if 0はプリプロセッサの指令であり、⁠#if」の右の制御定数式が偽(0)なので、この行から#endifの行までがプリプロセッサの段階でカットされ、コンパイラ本体には渡されなくなります。

条件コンパイル

同じソースファイルを用い、これをコンパイル時に指定する条件によって別の実行バイナリファイルを作成できるようにすることを条件コンパイルと言います。

条件コンパイルは、同じソースファイルを、複数のOSでコンパイルできるように、OSごとにことなる違いに対応させたり、あるいはプログラム中の一部の機能を有効にするかどうかをコンパイル時に指定したりするためなどに使います。

ソースコードが公開されたフリーソフトウェアのソースファイルを見てみると、多数の条件コンパイルが行われていることがわかるでしょう。

条件コンパイルが行われているプログラムの例をリストA2.2に示します。条件コンパイルはプリプロセッサのマクロを使い、該当のマクロが定義されているかどうかを#ifdefというプリプロセッサの指令で判断します。リストA2.2の例では、CONDITIONというマクロが定義されているかどうかによって、printf()が表示するメッセージが異なるようになっています。条件判断に使用しているマクロは、ソースファイル中で「#define」することもできますが、コンパイル中にgccのオプションでマクロ定義することもできます。

リストA2.2 ifdef_test.c
#include <stdio.h>

/* #define CONDITION */
/* #undef CONDITION */

int
main()
{
#ifdef CONDITION
  printf("CONDITION defined\n");
#else
  printf("CONDITION not defined\n");
#endif

  return 0;
}

gccのコマンドラインで、-Dマクロ名というオプションを付けると、そのマクロ名が定義されたものとしてプリプロセッサの処理が行なわれます。

なお、-D「マクロ名」との間はスペースを入れません。図A2.1の実行例のように、-DCONDITIONを付けてコンパイルすると「CONDITION defined」と表示され、何も付けないでコンパイルすると「CONDITION not defined」と表示されることが確認できます。

図A2.1 リストA2.2(ifdef_test.c)の実行例
$ gcc -DCONDITION -O2 -o ifdef_test ifdef_test.c
$ ./ifdef_test
CONDITION defined
$
$ gcc -O2 -o ifdef_test ifdef_test.c
$ ./ifdef_test
CONDITION not defined
$

おすすめ記事

記事・ニュース一覧