第14回
ヘッダファイルとプリプロセッサ指令

ヘッダファイルとプリプロセッサ

ヘッダファイルには、ソース中で用いる関数など様々な定義が記述されています。それを読み取ってコンパイラに引き渡すのがプリプロセッサです。

ヘッダファイルの中身

#include指令はそれが記述された箇所に指定したファイルを読み込んで展開する命令で、一般には拡張子.hのヘッダファイルが指定されます。Cで最も有名(?)なヘッダファイルは、おそらく“stdio.h”でしょう。stdioはstandard input/outputの略で、標準入出力に関する関数や定数、構造体 ※1 などの定義を行っています。

ここで、stdio.hの中身を少し覗いてみましょう。基本的にはどの処理系でもほぼ同じような定義が記述されていますが、書き方は処理系によって異なります。リスト1はLSI-C 86に用意されているstdio.hの一部です。

型の異なる複数の変数を1つにまとめて名前を付けたもの。詳しくは回を追って紹介します
リスト1:stdio.hの一部(LSI-C 86の場合)
      :
#ifndef __STDIO_H
#define __STDIO_H

#ifndef ANSI
#include <stddef.h>     /* for compatibility to older version */
#endif  /* ANSI */

#ifndef __SIZE_T_DEFINED
#define __SIZE_T_DEFINED
typedef unsigned size_t;
#endif

#ifndef __VA_LIST_DEFINED
#define __VA_LIST_DEFINED
typedef void    *va_list;
#endif

#ifndef __FPOS_T_DEFINED
#define __FPOS_T_DEFINED
typedef long    fpos_t;
#endif

#ifndef __FILE_DEFINED
#define __FILE_DEFINED
typedef struct  {
       
char    mode;           /*      file mode       R, W, R/W       */
       
char    *ptr;           /*      next character position         */
       
int     rcount;         /*      number of characters left       */
       
int     wcount;         /*      number of rooms left            */
       
char    *base;          /*      location of buffer              */
       
unsigned bufsiz;        /*      size of bufer                   */
       
int     fd;             /*      file descriptor                 */
       
char    smallbuf[1];    /*      used for buffer when unbufferd  */
}       FILE;
#endif
      
:

ヘッダファイルは#の連続

リスト1をよく見てみると、ほとんどの行が#で始まっています。stdio.hは単純に様々な定義を行っているわけではありません。Cの処理系には他にもたくさんのヘッダファイルが用意されており、1つのソースに複数のヘッダファイルが取り込まれることはよくあります。

それらヘッダファイルには、同じ名前の識別子(シンボル)が重複して定義されていることがあります。例えば記号定数のNULLは、標準入出力関数だけではなく文字列を扱う関数でも使われるため、文字列処理の関数を宣言しているstring.h内でも定義しておく必要があります。

そこで、記号定数の定義などで重複を防ぐため、
もし定数"xxxx"が定義されていなければ
"xxxx""ZZZZ"という名前で定義せよ
といった形の記述をします。ヘッダファイルが取り込まれる順序は一定ではないため、処理系に準備されているすべてのヘッダファイルで、このような定義の重複に対処する記述がされています。

プリプロセッサの制御構造

例えばリスト1には、以下のような記述があります。
#ifndef __STDIO_H
#define __STDIO_H
#ifdefは「もし、続く記号定数が定義(define)されていなければ、続く指令を処理せよ」という意味です。#defineは本来以下のような書式で、記号定数を定義します。

#define <識別子名> <値>
<値>を省略すると1とみなされます。従って上の2行は次のような意味になります。

もし"__STDIO_H"が定義されていなければ
"__STDIO_H""1"と定義せよ
この定義は、取り込まれた他のヘッダファイルが、既にstdio.hが取り込まれているかどうかを判断する際に用いられます。stdio.hが取り込まれていれば記号定数“__STDIO_H”が1と定義されているので、他のヘッダファイルで

#ifndef __STDIO_H
という行を記述すれば、そのことが判別できます。

このようにプリプロセッサ指令では、プリプロセッサの動作そのものに対して制御構造を記述できます。