10.3 プリプロセッサ(pre-prosessor)

  3.マクロ展開の例
(1) define 文
 #defineで定義されるものはすべて C プリプロセッサによって、展開されます。以下のプログラムでは、可読性のためにdefine文を利用しています。
#define     SUCCESS      1    /* 成功 */
#define     FAIL         0    /* 失敗 */

int do_something(){
  if(...){
      ....;
      return SUCCESS; /* 成功 */
  }
  /* 失敗 */
  return FAIL;
}
このプログラムはCプリプロセッサによって以下のように書き換えられます。 コメントも削除されます。この状態のもの(*.i中間ファイル)を、実際にcc1コンパイラがコンパイルします。
int do_something(){
  if(...){
      ....
      return 1;
  }

  return 0;
}
(2) include 文
 #includeによって指定されたファイルは、その行に挿入されます。stdio.hなどのヘッダファイルはCプリプロセッサによって挿入され、関数や変数の宣言を実現しています。例えば、hello.cプログラムの場合、中間ファイルhello.iはstdio.hに記述された全てのマクロ展開を行った結果となります(従って、非常に長い)。
<hello.c>

#include <stdio.h>

int main( void ) {
      printf("Hello, World!\n");
}
<hello.i>

# 1 "hello.c"
# 1 "/usr/include/stdio.h" 1 3
# 1 "/usr/include/features.h" 1 3
# 138 "/usr/include/features.h" 3
# 196 "/usr/include/features.h" 3
# 1 "/usr/include/sys/cdefs.h" 1 3
# 47 "/usr/include/sys/cdefs.h" 3
# 69 "/usr/include/sys/cdefs.h" 3
# 103 "/usr/include/sys/cdefs.h" 3
# 138 "/usr/include/sys/cdefs.h" 3
# 250 "/usr/include/features.h" 2 3
# 1 "/usr/include/gnu/stubs.h" 1 3
# 278 "/usr/include/features.h" 2 3
# 27 "/usr/include/stdio.h" 2 3
# 1 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 1 3
# 19 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3
# 61 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3
# 131 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3

typedef unsigned int size_t;

# 271 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3
# 283 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3
# 317 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3
# 33 "/usr/include/stdio.h" 2 3
# 1 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stdarg.h" 1 3

typedef void *__gnuc_va_list;

# 116 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stdarg.h" 3
# 202 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stdarg.h" 3
# 38 "/usr/include/stdio.h" 2 3
# 1 "/usr/include/bits/types.h" 1 3
# 1 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 1 3
# 19 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3
# 61 "/usr/lib/gcc-lib/i386-redhat-linux/egcs-2.91.66/include/stddef.h" 3

(途中、省略)

# 1 "hello.c" 2

int main( void ) {
      printf("Hello, World!\n");
}
(3) 記号定数
 例えば、__FILE__、__LINE__、__DATE__など、コンパイル時にすでに定義されているものでよく利用されているものを紹介します。前述1.2コンパイラの構成にて概観した、コンパイラオプション-vを用いて表示したコンパイル過程の詳細で、cppに-Dオプションにて引き渡されているパラメータも、この記号定数で使用可能なものです。
__FILE__ : ファイル名を指す
__LINE__ : この行数を示す
__DATE__ : コンパイルしている時刻を示す
 以下にそれぞれを表示しているプログラムの例と、プリプロセッサ処理後の結果を示します。
<test1.c>

#include <stdio.h>    /* 標準入出力ヘッダ ファイル */

int main(void){
  printf("%s(%d): %s\n", __FILE__, __LINE__, __DATE__);
  printf("%s(%d): %s\n", __FILE__, __LINE__, __DATE__);
  printf("%s(%d): %s\n", __FILE__, __LINE__, __DATE__);

  return 0;
}
<test1.i>

(省略)

# 1 "test1.c" 2

int main(void){

 printf("%s(%d): %s\n", "test1.c", 4, "Dec 12 2001");
 printf("%s(%d): %s\n", "test1.c", 5, "Dec 12 2001");
 printf("%s(%d): %s\n", "test1.c", 6, "Dec 12 2001");

 return 0;
}
 このプログラムをtest1.cに保存し、a.outとしてビルドした後、実行した場合の実行結果は以下のとおりです。 すべての記号定数は、それぞれ置き換わっていることに注目してください。特に__LINE__はその行に合わせて変更されています。ここに挙げた __FILE__ などはプリプロセッサによって特別に扱われる設定値ですので、自分では定義しないでください。
<実行結果>
# ./a.out
test1.c(4): Dec 12 2001
test1.c(5): Dec 12 2001
test1.c(6): Dec 12 2001