7-2 プリプロセッサ命令を使うときの注意点

この記事は約5分で読めます。

プリプロセッサ命令の文法

C言語では文の終わりにセミコロン;をつける必要がありますが、プリプロセッサ命令はコンパイラが直接解釈するものではないので、セミコロンをつける必要がありません。C言語ではフリーフォーマットのため改行やスペースなどは自由に入れることができますが、プリプロセッサ命令の途中で改行することはできません。しかし、例えば#define指令で定義したい関数マクロが非常に長い場合などは途中で改行したくなる場合があります。その時は円記号\をつけて改行することができます。C言語では円記号\は改行を取り消すという意味があります。

#include <stdio.h>
#define MAXOF(a,b)
	(a>b?a:b)
int main(void){
	int p,q;
	puts("整数を半角スペース区切りで二つ入力して
ください:");
	scanf("%d%d",&p,&q);
	printf("pとqの大きいほうは%d\n",MAXOF(p,q));
	return 0;
}

実行結果

コンパイルエラーにより実行できません
エラー表示(筆者の環境の場合)
エラー 3行目 ) が必要
エラー 6行目 文字列または文字定数が閉じていない
エラー 7行目 不正な文字 'く' (0x82ad)
エラー 7行目 不正な文字 'だ' (0x82be)
エラー 7行目 不正な文字 'さ' (0x82b3)
エラー 7行目 不正な文字 'い' (0x82a2)
エラー 7行目 文字列または文字定数が閉じていない

2行目の末尾に円記号を付けます。

#include <stdio.h>
#define MAXOF(a,b)\
	(a>b?a:b)
int main(void){
	int p,q;
	puts("整数を半角スペース区切りで二つ入力して\
ください:");
	scanf("%d%d",&p,&q);
	printf("pとqの大きいほうは%d\n",MAXOF(p,q));
	return 0;
}

実行結果

整数を半角スペース区切りで二つ入力してください:
2 3
pとqの大きいほうは3

円記号\を使用するとプリプロセッサ命令を2行以上にわたって記述することができます。円記号\が改行取り消すというのは、プリプロセッサ命令に限らずC言語のルールなので、文字列リテラルの途中で改行することもできます。しかし、この書き方はあまり一般的ではないのでお勧めしません。文字列リテラルでの途中で改行する場合は途中でダブルコーテーションを挟んで二つに分ける書き方が一般的です。

関数マクロの副作用

関数マクロ使用するとき気をつけなければならない点があります。

#include <stdio.h>
#define PRODUCT(a,b) (a*b)
int seki(int a,int b) {return a*b;}
int main(void){
	int p,q;
	puts("整数を半角スペース区切りで二つ入力してください:");
	scanf("%d%d",&p,&q);
	printf("(p+1)*(q+1)=%d\n",seki(p+1,q+1));
	printf("(p+1)*(q+1)=%d\n",PRODUCT(p+1,q+1));
	return 0;
}

実行結果

整数を半角スペース区切りで二つ入力してください:
2 9
(p+1)*(q+1)=30
(p+1)*(q+1)=12

p=2,q=9としたときにp+1=3,q+1=10なので(p+1)x(q+1)=30となるはずです。しかし、マクロPRODUCTを使用すると12となっています。これはマクロが関数ではなく単なる式の置換だからです。

PRODUCT(p+1,q+1)は、(p+1*q+1)と置換されます。

つまり、2+1*9+1=12となり、12が出力されます。一方、関数のほうはp+1,q+1の値がそれぞれ仮引数にコピーされるので、このようなバグは発生しません。

このようにマクロが意図した通りに作用しないことをマクロの副作用といいます。今回の場合、マクロ展開する式のオペランドに括弧をつけると正しい答えが得られます。

#include <stdio.h>
#define PRODUCT(a,b) ((a)*(b))
int seki(int a,int b) {return a*b;}
int main(void){
	int p,q;
	puts("整数を半角スペース区切りで二つ入力してください:");
	scanf("%d%d",&p,&q);
	printf("(p+1)*(q+1)=%d\n",seki(p+1,q+1));
	printf("(p+1)*(q+1)=%d\n",PRODUCT(p+1,q+1));
	return 0;
}

実行結果

整数を半角スペース区切りで二つ入力してください:
2 9
(p+1)*(q+1)=30
(p+1)*(q+1)=30

実はこれでもうまくいかない例が存在します。

#include <stdio.h>
#define PRODUCT(a,b) ((a)*(b))
int seki(int a,int b) {return a*b;}
int main(void){
	int p,q;
	puts("整数を半角スペース区切りで二つ入力してください:");
	scanf("%d%d",&p,&q);
	printf("(p+1)*(q+1)=%d\n",seki(p+1,q+1));
	printf("(p+1)*(q+1)=%d\n",PRODUCT(++p,q++));
	return 0;
}

実行結果

整数を半角スペース区切りで二つ入力してください:
2 9
(p+1)*(q+1)=30
(p+1)*(q+1)=27

インクリメント演算子を使用すると、27と表示されました。27=3*9なので、(p+1)xqを計算していることになります。++pは評価すると、pに1を足した後の値になるのに対して、q++は評価すると、qに足す前の値になります。よって3×9が計算されます。

このように、マクロを使用する場合は副作用に注意する必要があります。

まとめ

プリプロセッサ命令ではセミコロンを付ける必要はない。

基本的に、プリプロセッサ命令の途中で改行してはいけない。

どうしても改行したい場合は\記号を用いる。

マクロを使用するときは、副作用に注意する。

コメント

タイトルとURLをコピーしました