D.プリプロセス命令(#で始まる行について)
Copyright(C) 27Jan2003 coskx
このページではよく使われるプリプロセス命令(#で始まる行)について解説している。
内容は以下のとおりである。
1 #include
"stdio.h" などの #include
2 #define による定数定義
3 #define による変数型定義
4 #define
によるマクロ
5 #if による条件コンパイル
D.1 #include
「#include "stdio.h"」あるいは「#include
<stdio.h>」について,Cを学び始めの頃は「おまじない」なのでこれの意味は追求しないこととしていた。
「#include
"stdio.h"」 とは,Cプログラムソース中で,「#include
"stdio.h"」が書かれている位置に,「stdio.h」という名前のファイルを読み込んでから,コンパイルを始めなさいという意味である。ちなみにstdio.hは「standard
I/O」(I/OはInput/Outputの意味)に関する必要な定義や関数のプロトタイプ宣言が書かれているヘッダファイル(header
file)である。
「.h」はヘッダファイルを意味する。また「include」というのは「〜を(…の中に)含める」という意味である。
「stdio.h」という名前のファイルはどこにあるかというと,どのコンパイラを使っているのかに依存するが,コンパイラのあるフォルダの近くにある
「include」というフォルダ内にある場合が多い。例えば,WindowsPCでファイル検索を全ハードディスクを対象に行うと見つけることが出来
る。数百行のテキストファイルであるが,この数百行が,自分で書いたプログラムソースの「#include
"stdio.h"」が書かれている位置に読み込まれてからコンパイルが始まる。コンパイルの直前に行われる作業なので「プリプロセス」と呼ばれる
ところでコンパイルとは何かというと
Cプログラムソースを機械語に変換する最初の段階の作業のことである。通常はコンパイル作業後にリンクといって実行に必要な機械語コードを結合して実行可能なファイルが作られる。
どのような時に,このファイル「stdio.h」を必要とするかというと
(1)printfなどの画面表示とscanfなどのキーボード入力(コンソール入出力とも呼ばれる)の関数を呼ぶ時
(2)fopen,fprintf,fputs,fpuc,fcloseなどのファイルの入出力関係
である。
このほかに
#include "math.h"
は数学関係の関数(sqrt,sin,cos,tan)を使う時
#include "stdlib.h" はexitなどの関数を使う時
#include
"ctype.h" は文字処理関係の関数(isupper,toupper)を使う時
#include "string.h"
は文字列処理関係の関数(strlen,strcpy,strcat)を使う時
に使用される。関数のライブラリマニュアルをみると,その関数を使う時にどのヘッダファイルを使うように宣言すればよいかわかる。
これらのヘッダファイルはコンパイラ供給元が,コンパイラの仕様の差を吸収するために使っていることもあるので,コンパイラAに付属しているヘッダファイルをコンパイラBでの使用時に使うことは出来ない。そのため,通常はヘッダファイルの中身を変更してはいけない。
D.2 #define
による定数定義
Cプログラム中で,定数をそのまま書くとその定数の意味がわからなかったり,プログラムの定数変更時に,定数の変更忘れが起こる場合がある。これを防ぐために#define
による定数定義を行なう。
#define による定数定義はコンパイル作業の直前の作業であるプリプロセス時に,数値に差し替えられる。
例えば次の例でPIはプリプロセッサで3.14...に展開されてからコンパイルされる
List D.2.1 #defineでπを表現したプログラム |
#include <stdio.h> |
radius=10.000000 area=314.159265 |
List D.2.2
プリプロセッサがPIを数値に展開したイメージ 通常はこの状態を見ることは出来ない (コメントも取り払われている) |
#include <stdio.h> |
例えば次の例を見てみよう
List D.2.3 ソートのプログラム中の二重ループでスナップショット |
#include <stdio.h> int main() |
173.2 168.5 178.1 183.7 164.2 (0,1) (0,2) (0,3) (0,4) (1,2) (1,3) (1,4) (2,3) (2,4) (3,4) 183.7 178.1 173.2 168.5 164.2 |
ここで
double
height[5]={
173.2,168.5,178.1,183.7,164.2
};/*cm*/
を10個のデータに変更したい場合,何箇所の変更が生ずるか考えてみると不便さがわかる。
このプログラムを次のように書き直しておくと
List D.2.4 ソートのプログラム中の二重ループでスナップショット |
#include <stdio.h> #define SIZE 5 int main() |
|
10このデータを使ったプログラムに変更する時でも#defineを書き換えるだけですむ。
D.3 #define
による変数型定義
Cプログラム中で,例えばunsigned
charという型名を毎回書かされるのはいやだということがある。
この様な場合には
#define UCHAR unsigned char |
UCHAR c1,c2,c3; |
D.4 #define
によるマクロ
Cプログラム中で,短い作業を関数化せずに#defineを使ってマクロ命令を作ることがある。しかし,#defineによるマクロ命令は後で述べるように危険な副作用があるので,引数を伴う使用はやめるべきである。
これは使っても安全なマクロ命令
List D.4.1 PRINTDATA()を関数のように見せているが実はマクロ命令 main中に展開されてからコンパイルされる。 あまりご利益はないので,すなおに関数にしたほうがよい。 |
#include <stdio.h> #define SIZE 5 int main() |
173.2 168.5 178.1 183.7 164.2 183.7 168.5 173.2 178.1 164.2 183.7 178.1 168.5 173.2 164.2 183.7 178.1 173.2 168.5 164.2 183.7 178.1 173.2 168.5 164.2 183.7 178.1 173.2 168.5 164.2 |
List D.4.2 2乗を計算するSQUARE()関数をマクロで作る。 実行結果をみると,うまくいっているように見える。 |
#include <stdio.h> #define SQUARE(x) x*x int main() |
x=4.000000 y=16.000000 |
ここまではよいが次の例を見てみよう。
List D.4.3 2乗を計算するSQUARE()関数をマクロで作る。 yは50になるはずでは? |
#include <stdio.h> #define SQUARE(x) x*x int main() |
x=4.000000 y=10.000000 |
実は |
List D.4.4 2乗を計算するSQUARE()関数をマクロで作る。 これで解決したかも? |
#include <stdio.h> #define SQUARE(x) ((x)*(x)) int main() |
x=4.000000 y=50.000000 |
#define SQUARE(x)
((x)*(x))を使えば |
List D.4.5 2乗を計算するSQUARE()関数をマクロで作る。 |
#include <stdio.h> #define SQUARE(x) ((x)*(x)) int main() |
x=6.000000 y=16.000000 |
x=5 y=16を期待していたが,x=6になってしまった。 |
#defineを用いたマクロ命令は,このように危険があるため,使用は勧められないが,
他人が作成したプログラムを読む時には必要なので解説した。
使うときは,文字がそのまま展開されることに注意が必要である。
D.5 #if
による条件コンパイル
プログラムのデバッグの時にはある部分をコンソールに出力し,デバッグが終わったらその部分は出力したくないといった場合に,条件に応じてコンパイルすると便利である。
List D.5.1 ソート部分をデバッグするときにはその部分を表示させたい |
#include <stdio.h> #define SIZE 5 int main() |
173.2 168.5 178.1 183.7 164.2 (0,1) (0,2) (0,3) (0,4) (1,2) (1,3) (1,4) (2,3) (2,4) (3,4) 183.7 178.1 173.2 168.5 164.2 |
List D.4.2 「List D.4.1 」がプリプロセッサ作業後には |
: int main() |
List D.5.3 ソート部分のデバッグが終わったので,その部分を表示させたくない |
#include <stdio.h> #define SIZE 5 int main() |
173.2 168.5 178.1 183.7 164.2 183.7 178.1 173.2 168.5 164.2 |
List D.4.4 「List D.4.3 」がプリプロセッサ作業後には |
: int main() |
このように#define
ONDEBUGの有無でコンパイル直前にソースプログラムを書き換えたのと同様の効果を得ることが出来る。
なお
#define
ONDEBUG
のところは
#define ONDEBUG 10
のように数値が定義されていてもよい。
このほかに
#define ABCD : #ifdef ABCD /*ABCDが定義されていたら*/ 〜 #else /*そうでなかったら*/ 〜 #endif |
#define ABCD : #ifndef ABCD /*ABCDが定義されていなかったら*/ 〜 #else /*そうでなかったら(定義されていたら)*/ 〜 #endif |
#define ABCD 100 : #if ABCD==100 /*ABCDが100に定義されていたら*/ 〜 #else /*そうでなかったら*/ 〜 #endif |
#define ABCD : #if defined(ABCD) /*ABCDが定義されていたら*/ 〜 #else /*そうでなかったら*/ 〜 #endif |
#define ABCD #define EFGH : #if defined(ABCD) && defined(EFGH) /*ABCDとEFGHの両方が定義されていたら*/ 〜 #else /*そうでなかったら*/ 〜 #endif |
#define ABCD #define EFGH : #if defined(ABCD) || defined(EFGH) /*ABCDまたはEFGHが定義されていたら*/ 〜 #else /*そうでなかったら*/ 〜 #endif |
のような条件コンパイルも出来る。
定義の抹消
#define ABCD : #undef ABCD /*ABCDの定義抹消*/ : |
#define ABCD 10 : #ifdef ABCD #undef ABCD /*ABCDの定義抹消*/ #define ABCD 20 /*ABCDを再定義*/ #endif : |
のように使うことも出来る。
二重定義はコンパイルエラーになるのでこの方法で回避策をとることもできる。
πがどこかで3.14などと定義されているかもしれない。 桁数を増やして定義しなおしたい場合は次のように行なう |
#include ・・・・ #ifdef PI |