C言語編 第28章 マクロの活用

このエントリーをはてなブックマークに追加

この章の概要

この章の概要です。

関数形式マクロ

第24章で、#define を使った、オブジェクト形式マクロを説明しました。 今回説明する関数形式マクロも、#define を使って実現するマクロですが、少し形が異なります。

関数形式マクロは、使用時の記述が、関数を呼び出しているように見えるため、このように呼ばれます。 関数形式マクロの定義は、次のように行います。

#define マクロ名(引数のリスト) 置換後の文字の並び

マクロ名の直後に ( ) で引数を指定する点が、オブジェクト形式マクロとの違いです。 引数といっても、ここに指定するのは引数の名前だけで、型はありません。 以前説明したように、#define はプリプロセスで処理されるものであって、C言語の文法とは無関係なのです。 ですから、型という考え方自体がありません。
なお、( ) の中を空にすることもできます。 この場合、引数は存在しないということになります。 これは、引数が void型の関数のように見せかける際に役立ちます。

実際の使用例を見てみましょう。

#include <stdio.h>
#include <string.h>

#define STR_EQ(s1,s2)		strcmp(s1,s2)==0

int main(void)
{
	if( STR_EQ( "abc", "abc" ) ){
		puts( "OK" );
	}
	
	if( !STR_EQ( "abc", "ab" ) ){
		puts( "OK" );
	}
	
	return 0;
}

実行結果:

OK
OK

文字列同士の一致を調べるマクロを定義しました。 strcmp関数(⇒リファレンス)は、一致したときに偽(0) を返す仕様ですが、 このマクロを使うと、自然な形で(一致したときに真)判定できます。

プリプロセスの過程で置換されるので、このプログラムは実際には次のような形でコンパイルされます。

#include <stdio.h>
#include <string.h>

int main(void)
{
	if( strcmp("abc","abc")==0 ){
		puts( "OK" );
	}
	
	if( !strcmp("abc", "ab")==0 ){
		puts( "OK" );
	}
	
	return 0;
}

実行結果:

OK
OK

結果を更に否定している方に、やや違和感があるかも知れません。 このままでも正しく動作はしますが、一般的には、マクロの置換結果全体を ( ) で囲むべきです。 そうすることで、演算子の優先順位の兼ね合いで予期しない結果になることを防ぐ効果があります。
つまり、次のように定義します。

#define STR_EQ(s1,s2)		(strcmp(s1,s2)==0)

この場合、置換結果は次のようになります。

if( !(strcmp("abc", "ab")==0) ){
	puts( "OK" );
}


通常の関数と異なり、マクロの場合はプリプロセスの段階で置換済みであることを忘れないようにしましょう。 関数を呼び出すという行為そのものにも、処理時間が掛かるのですが、マクロの場合は、この呼び出しが発生しませんから、処理効率は向上します。
一方で、マクロを使っている箇所が個別に置換されるため、使用箇所が多いと、プログラムの大きさは増大しやすくなります。

C99 (可変個引数マクロ)

C99 では、関数形式マクロの引数を可変個数にできます。 これは、printf関数のように、引数の型や個数を任意に指定できるということです。
VisualC++ 2013/2015/2017、clang 3.7 ともに、使用できます。

この機能に関する詳細は、第52章で取り上げます。

C99 (空の実引数)

C99 では、関数形式マクロの実引数を空白にすることが許可されるようになりました。
VisualC++ 2013/2015/2017、clang 3.7 ともに、使用できます。

#include <stdio.h>

#define CAT_VALUE(value, suffix)  value ## suffix

int main(void)
{
	int iValue = CAT_VALUE(10, );
	long int lValue = CAT_VALUE(10, L);
	long long int llValue = CAT_VALUE(10, LL);

	printf( "%d\n", iValue );
	printf( "%ld\n", lValue );
	printf( "%lld\n", llValue );
	
	return 0;
}

実行結果:

10
10
10

この例では、整数定数に付けるサフィックスを、##演算子による連結で実現しています。 このとき、サフィックスが不要な場合には、2つ目の実引数を空にできます。
##演算子 については、「##演算子(トークン連結演算子)」で解説しています。

C99 (inline関数)

C99 で inlineキーワードが追加されました。 関数宣言時に、このキーワードを付加することで、関数の呼び出しコストを避けられる可能性があります。
ただし、VisualC++ 2013 は、この機能には対応していません。 2015/2017 では、正式に対応しているという記述が発見できていませんが、少なくともコンパイルエラーは起きなくなっています。 clang 3.7 は対応しています

VisualC++ の場合、inline の代わりに __inline というキーワードを使えば同じ結果を生みます。 これは Microsoft の独自拡張です。

#include <stdio.h>
#include <string.h>

inline int str_eq(const char* s1, const char* s2);

int str_eq(const char* s1, const char* s2)
{
	return strcmp( s1, s2 ) == 0;
}

int main(void)
{
	if( str_eq( "abc", "abc" ) ){
		puts( "OK" );
	}
	
	if( !str_eq( "abc", "ab" ) ){
		puts( "OK" );
	}
	
	return 0;
}

実行結果:

OK
OK

inlineキーワードを関数宣言のところにだけ付け、定義の方には付けません。 このキーワードが付加された関数は inline関数と呼ばれ、 関数呼び出しをできる限り高速に行うような最適化を試みます。
具体的な最適化の手法までは規定されていませんが、関数の中身を呼び出し元のところに展開する方法が一般的です。 そのため、関数呼び出しのコストが無くなりますが、呼び出し箇所が多ければプログラムサイズが大きくなる可能性があります。 要するに、マクロが展開される場合と同じような結果を生む訳ですが、inline を使う場合は、あくまでも関数なので、 引数や戻り値に型指定ができる点が優れています。

なお、inlineキーワードは、コンパイラの判断によって無視されることがあります。 その場合、普通の関数として扱われます。

関数の中身が展開されるのならば、inline関数を呼び出している箇所から、関数の中身が見えなければいけません。 あるソースファイルに関数定義を、対応するヘッダファイルに関数宣言を書くというスタイル(当サイトの基本的なスタイルです)の場合、 別のソースファイルからはヘッダファイルしか見えていませんから、展開できません。
例えば、VisualC++ で __inlineキーワードを使って試すと、次のような場合にエラーになります。 (サンプル中では C99規格に合わせて inlineキーワードを使っています)。
clang では、このプログラムでも問題ありません。

/* sub.h */

inline int str_eq(const char* s1, const char* s2);
/* sub.c */

#include <string.h>
#include "sub.h"

int str_eq(const char* s1, const char* s2)
{
	return strcmp( s1, s2 ) == 0;
}
/* main.c */

#include <stdio.h>
#include "sub.h"

int main(void)
{
	if( str_eq( "abc", "abc" ) ){
		puts( "OK" );
	}
	
	if( !str_eq( "abc", "ab" ) ){
		puts( "OK" );
	}
	
	return 0;
}

main.c からは str_eq関数の中身が見えないので、展開が行えずにコンパイルエラーになります。
解決方法として、まず inline関数の中身をヘッダファイル内に書くようにします

/* sub.h */

#include <string.h>

inline int str_eq(const char* s1, const char* s2)
{
	return strcmp( s1, s2 ) == 0;
}

これは inline定義と呼びます。 ここで用意した関数が、中身を展開するときに使われます。 もし、コンパイラが、中身を展開しないことを選んだ場合には、sub.c に定義されている str_eq関数の宣言として使われます。

一方、sub.c は、次のように書きます。

/* sub.c */

#include "sub.h"

extern inline int str_eq(const char* s1, const char* s2);

sub.h を include しているので、inline定義を取り込んでいます。 また、extern が付いているので、sub.c には関数の定義はなく、外部に定義があると明示されます。 ここでいう外部の定義というのは、inline定義のことを指しています。

これでプログラムがコンパイルできるようになりました。

実行結果:

OK
OK

マクロ使用時の注意点

関数形式マクロは便利な反面、うまく使わないと予期せぬ動作をしてしまいます。

まず 1つ目の問題は、引数に型がないことです。 引数の名前は宣言しますが、その型は宣言しませんから、どんな型であっても引数として使用できてしまうのです。 そのため、使うときには間違った型を指定しないように注意する必要があります。
もちろん、プリプロセスで置換が行われた後、C言語のルール通りにコンパイルされるので、その段階で型が明らかに正しくなければ、 コンパイルエラーにはなります。

2つ目の問題は、計算順序です。 例えば、次の関数形式マクロはどうでしょう?

#define COMPUTE(a,b) a+b*10

このマクロを、次のように使ったとします。

int num1 = COMPUTE( 5, 2 );
int num2 = 3 * COMPUTE( 5, 2 );

これは次のように置換されます。

int num1 = 5+2*10;
int num2 = 3 * 5+2*10;

1行目の方は、問題なく 25 という結果になりますが、 2行目の方は、35 になります。意図通りなら、1行目の結果の 3倍になるはずですから、75 が正解でしょう。

置換された結果から分かるように、本当は、「3 * (5+2*10)」となってほしいところが、 「(3*5) + (2*10)」のようになってしまっています。計算の優先順位が、意図した通りになっていません。

この問題は解決可能です。マクロを定義するときに、全体を ( ) で囲むのです。

#define COMPUTE(a,b) (a+b*10)

すると、先ほどの 2つの呼び出しは、次のように展開されることになります。

int num1 = (5+2*10);
int num2 = 3 * (5+2*10);

これで意図通りに計算されます。 このように、マクロの置換後の文字の並びには、( ) もちゃんと含まれます。
ところが、これだけでもまだ対応不足です。 次のように呼び出されると困ります。

int num3 = COMPUTE( 5, num1+3 );

num1 は先ほどの続きで 25 になっているとしましょう。 この場合、意図としては、「5 + (25+3)*10」なので、285 になってほしいのですが、実際には、次のように置換されてしまいます。

int num3 = (5+num1+3*10);

従って、この計算結果は 60 になってしまいます。 この問題への解決策は、マクロの定義時に、すべての引数を ( ) で囲むことです。

#define COMPUTE(a,b) ((a)+(b)*10)

すると、次のように置換されるようになります。

int num3 = ((5)+(num1+3)*10);

なかなか面倒ではありますが、置換結果全体と、引数それぞれを ( ) で囲むことは、関数形式マクロを使う上では必須事項です

最後に、3つ目の問題です。 今度は違うマクロを用意します。

#define MAX(a,b) ((a) > (b) ? (a) : (b))

2つの引数を持ち、大きい方の値を返すマクロです。 次のように使います。

int a = 10;
int ans1 = MAX(a, 5);
int ans2 = MAX(++a, 5);

ans1 の方は何も問題なく、10 が代入されますが、ans の方は問題です。 置換結果は次のようになります。

int ans2 = ((++a) > (5) ? (++a) : (5));

条件判定の際に、a はインクリメントされるので、11 と 5 を比較することになります。 大きいのは 11 の方なので、++a が返されますが、ここが問題です。 ++a なので、ここでもう1度インクリメントされてしまうのです。
つまり、11 と 5 の大きい方を返すはずなのに、結果は 12 になります。

このインクリメント(もちろん、デクリメントも)による問題の解決策は存在しません。 注意して使うしか方法はないのです。 ただしこれは、使う際に注意が必要であるという事実を述べただけであって、MAXマクロのようなマクロは一般的にもよく使われているものです。 決して、使い物にならないということではありません。

C++ であれば、インラインのテンプレート関数を使うことが、マクロの問題点に対する有効な解決策になります(C++編【言語解説】第9章)。 また、型が明確であれば、C99 のインライン関数で代用することも有効です。

#演算子(文字列化演算子)

#演算子(文字列化演算子)は、マクロの置換後の文字の並びの中でのみ使用できます。 この演算子は、マクロの引数を、"" で囲んだ文字列リテラルに置き換える効果があります

#include <stdio.h>

#define LOG_INT(var)	printf(#var ": %d\n", var)

int main(void)
{
	int num1 = 123;
	int num2 = -350;

	LOG_INT( num1 );
	LOG_INT( num2 );
	
	return 0;
}

実行結果:

num1: 123
num2: -350

LOG_INTマクロは、引数に符号付き整数を与えると、その名前とともに、現在の値を出力します。 名前を出力するために #演算子を使っています。

このように、変数の名前を文字列化したものを作り出せる点が、#演算子の便利さでしょう。

##演算子(トークン連結演算子)

##演算子(トークン連結演算子)も、マクロの置換後の文字の並びの中でのみ使用できます。 この演算子は、## の前後にある字句を連結します

ここで、字句というのは、ソースコード上の文字のことを指しています。 "" で囲まれた文字列のことではありません。

#include <stdio.h>

#define CAT(first,second)	first ## second

int main(void)
{
	int num1 = 10;
	int num2 = 20;
	int num3 = 30;

	printf( "%d\n", CAT(num, 1) );
	printf( "%d\n", CAT(num, 2) );
	printf( "%d\n", CAT(num, 3) );
	
	return 0;
}

実行結果:

10
20
30

連結された結果、出来あがった文字の並びが、変数名や関数名などの何らかの有効な言葉になっていなければなりません。 そうなっていない場合の扱いは、コンパイラに依存します。

また、先ほどのサンプルプログラムの printf関数の呼び出しの部分を、

for( i = 1; i <= 3; ++i ){
	printf( "%d\n", CAT(num, i) );
}

このように変えると正しく動作しません。 これだと、CATマクロで連結された結果は「numi」になってしまいます。
こうなってしまう理由は、何度も書いているように、「マクロはプリプロセスで置換されているから」です。 それに対し、for文が変数i をインクリメントするのは、プログラムの実行時ですから、まるで次元の違う話なのです。
何とか for文と組み合わせて楽をできないかと考えてしまいますが、残念ながら不可能です。

アサートマクロ

最後に、標準ライブラリに用意されているマクロを1つ紹介しておきます。

assert (⇒リファレンス)というマクロは、 デバッグの助けとして非常に有益です。 assert は「アサート」と読み、「表明する」という意味です。
このマクロは、プログラム内の任意の場所で、「この時点でこうなっていなければならない」という予定を表明するものです。 プログラムを実行したとき、もし、その予定通りの状態になっていなければ、プログラムをその場で停止させます。

実際に試してみましょう。

#include <stdio.h>
#include <assert.h>

int divide(int num, int d);

int main(void)
{
	printf( "%d\n", divide( 100, 0 ) );
	
	return 0;
}

int divide(int num, int d)
{
	assert( d != 0 );  /* 0 で除算することはできない */
	
	return num / d;
}

実行結果:

Assertion failed: d != 0, file c:\main.c, line 15

assertマクロを使用するには、assert.h を include する必要があります

このプログラムを実行すると、すぐに停止してしまいます。 これは、assertマクロに指定した条件を満たしていないからです。 assertマクロの実引数が、偽になったとき、プログラムは abort関数によって強制的に停止されるのです。 もし、実引数が真になれば、assertマクロは何もしません

abort関数(⇒リファレンス)は、stdlib.h に定義されている標準関数で、プログラムを異常終了させるものです。 もちろん、stdlib.h を include すれば、プログラマの意思で呼び出すことができます。

abort関数は、あくまでも異常時の強制終了の手段であって、普通にプログラムを終了させる目的で使うものではありません。 正常な終了は、普通に main関数から return するか、exit関数(⇒リファレンス)を使います。

また、assertマクロは、NDEBUG (⇒リファレンス)というマクロ(記号定数)が定義されていない場合にだけ有効になります。 具体的には、次のような感じになっています。

#ifdef NDEBUG
#define assert(b)
#else
#define assert(b) 実装コード
#endif

このように、NDEBUG が定義されていると、assertマクロは空定義に置き換わるわけです。

NDEBUGマクロは、VisualC++ を使っているのなら、ビルド構成を変更すれば自動的に定義されます。 VisualC++ なら、メニューバーの「ビルド」→「構成マネージャ」をクリックし、「アクティブ ソリューション構成」を「Release」に変更します。
Xcode 4.6 の場合は、自分で設定しないと、NDEBUG が定義されることはありません。 Releaseビルド時にだけ定義するように指定するとすれば、 [TARGETS] -> [Build Settings] -> [Apple LLVM compiler 4.2 - Preprocessing] -> [Preprocessor Macros] の [Release] のところに、[NDEBUG] と入力して下さい。

NDEBUGマクロを、デバッグ作業を行っている段階では定義せず、リリース版のビルドを行うときに定義するようにしていれば、 デバッグ作業を行っている段階では assertマクロは有効になり、 最終的な完成品では、無効(空定義)になるということです。 従って、完成品では assert の効果によってプログラムが停止することはありません。

assert が、偽のときに停止するという部分が最初は分かりづらいかも知れません。 たまに逆の意味で使うアサートを独自で作っている人や、そもそも条件式のないものを作っている人(必ず停止する)がいますが、それはアサートとは呼べません
そういうマクロを作っていけない訳ではありませんが、その場合、アサートという名前を付けてはいけません。

独自のアサート

自分で都合の良いようにアサートを作ることも可能です。 標準の assertマクロで出力される内容は、条件式、ファイル名、行数の3つの情報ですが、ここに更に自分の好きなメッセージを追加できるようにしてみましょう。 そのためには、次のように定義します。

#ifdef NDEBUG
#define myassert(b,str)
#else
#define myassert(b,str)	\
	{if(!b){printf("assert: %s \n%s\n%sの%d行目\n", #b, str, __FILE__, __LINE__); abort(); }}
#endif

この myassertマクロは、2つの引数をとります。 1つ目は、標準の assertマクロと同じく条件式で、これが偽になったときに停止します。 2つ目の引数は、停止した場合に出力する文字列です。

行の末尾にある \ は、ここで次の行へ移ることを表す記号です。必ず末尾に書かなければなりません。 単に見やすくするために使っているだけですが、マクロの置換後の文字列の中で改行したければ、この記号を使うしかありません。
また、__FILE__ と __LINE__ は、標準で既に定義されているマクロで、前者はソースファイルの名前に、後者は現在位置の行数に置換されます。 詳細は、第29章で説明します。

さて、この myassertマクロを使って、先ほどのプログラム例を書き換えるとこうなります。

#include <stdio.h>
#include <stdlib.h>

#ifdef NDEBUG
#define myassert(b,str)
#else
#define myassert(b,str)	\
	{if(!b){printf("assert: %s \n%s\n%sの%d行目\n", #b, str, __FILE__, __LINE__); abort(); }}
#endif

int divide(int num, int d);

int main(void)
{
	printf( "%d\n", divide( 100, 0 ) );
	
	return 0;
}

int divide(int num, int d)
{
	myassert( d != 0, "0 で除算することはできない" );
	
	return num / d;
}

実行結果:

assert: d != 0
0 で除算することはできない
c:\main.cの21行目

実行結果にあるように、引数で指定したメッセージを出力できるので、停止した理由がすぐに分かります。

経験を積んだプログラマの中は、独自のアサートを作っている人が多くいます。 今回の例は、非常に単純な拡張ですが、単なる文字列ではなく printf関数の形式で文字列を渡せるようにしたり、 すぐに停止させてしまうのではなく、停止してもいいか確認してから停止させるようにしたりできます。 停止させないことを選択すると、そのアサートを無視して実行を続行させる訳です。
とはいえ、標準の assertマクロでも充分に有益です。 ぜひ使ってみて下さい。 使いにくいと感じるようになってきたら、オリジナルのものを作ってみるといいでしょう。


練習問題

問題@ 引数で指定された値の絶対値を返す関数形式マクロを作って下さい。

問題A 配列の名前を引数に与えると、その配列の要素数を返すような関数形式マクロを作成して下さい。

問題B 引数で指定された2つの変数を交換する関数形式マクロを作成して下さい。

問題C 次のプログラムはコンパイル可能ですか? 可能であるとしたら、どのような実行結果になりますか?

#include <stdio.h>

#define CALC(a,b,op)		a ## op ## b
#define CALC_STR(a,b,op)	#a ## #op ## #b

int main(void)
{
	printf( "%s=%d\n", CALC_STR( 10, 2, + ), CALC( 10, 2, + ) );
	printf( "%s=%d\n", CALC_STR( 10, 2, - ), CALC( 10, 2, - ) );
	
	return 0;
}


解答ページはこちら

参考リンク

更新履歴

'2017/3/25 VisualC++ 2017 に対応。

'2016/10/15 clang の対応バージョンを 3.7 に更新。

'2015/10/12 clang の対応バージョンを 3.4 に更新。

'2015/9/5 VisualC++ 2012 の対応終了。

'2015/8/24 Xcode での NDEBUGマクロの定義について追記。
リンク先を修正。

'2015/8/18 VisualC++ 2010 の対応終了。

'2015/8/15 VisualC++ 2015 に対応。

'2014/10/18 clang 3.2 に対応。

'2014/2/1 VisualC++ 2013 に対応。

'2014/1/14 VisualC++ 2008 の対応終了。

'2014/1/11 clang 3.0 に対応。

'2013/4/26 「C99 (空の実引数)」の項を追加。

'2013/4/20 関数形式マクロの引数リストは、空にできることを追記。

'2013/4/17 C99 の可変個引数マクロについての項を追加。

'2013/4/15 C99 のインライン関数についての項を追加。

'2013/4/14 「関数形式マクロ」の項に、定義全体を ( ) で囲むことについて追記。

'2013/3/2 #演算子、##演算子の日本語表記での呼び名を併記した。

'2009/9/19 新規作成。



前の章へ

次の章へ

C言語編のトップページへ

Programming Place Plus のトップページへ