KC's notebook

マクロ

マクロとは?

マクロとはプリプロセッサの為の命令であり、コンパイルされる前に実行されます。具体的に#defineのことです。

ここではC言語の知識を深めてもらうというよりは、使い方を学んでもらうつもりで書いているので、これ以上の詳しい話は割愛させて頂きます。(私自身これ以上の理解をしていないなんて言えない……)

マクロの注意事項

理解したつもりでいて、結局後で痛い目みるのがこのマクロ。なのでマクロについて解説していきたいと思います。

マクロの機能として良く知られているのが、置換です。置換はその名の通り置き換えることで、

#include <stdio.h>
#define HELLO "Hello,World"

int main( void ){
    printf( "%s" , HELLO );
    return 0;
}

といったプログラムをコンパイルすると、実行結果は、

Hello,World

となります。"HELLO"という文字列が、#defineによって"Hello,World"に置き換えられたことがわかるかと思います。つまり、

#define str1 str2

となっていた場合、ソース上のどこかに"str1"が有れば"str2"に置き換わる、ということなのです。

また、defineの置換は引数付けることが可能です。

#define plus( x ) ( x + x )

のようなマクロを書くと"plus( n )"のように引数をとることが出来るようになり、n + nした値を返すことができます。このようなマクロを関数マクロとも呼ばれたりします。

さて、ここで勘違いしないで頂きたいのが、文字列が置き換わるというところです。「えっ?数値は置き変えれらないの?」って思う方がいるかもしれませんが、そういう意味ではないんです。ここでいう"文字列"という言葉は、C言語的な文字列という意味ではなく、ソースコード的な意味での"文字列"なのです。つまりは、

#define A int main( void )
A{
    printf( "Hello,World" );
    return 0;
}

と書いても、コンパイルは通るんです(gccで確認。他のコンパイラだと駄目かも)。これは、"A"という部分が"int main( void )"と置き換わるため、

int main( void ){
    printf( "Hello,World" );
    return 0;
}

となるのです。これは非常にまずいと思いませんか?関数の書き方が決まっているのに、マクロを悪用すると先のようなプログラムでもコンパイルが通ってしまうのです。勿論これは極端な例なのでよほどのことが無い限り(例外処理とか)こんな書き方はしませんし、これを読んで下さっている方もこんな書き方はしないとは思います。ですが、実際に起こり得るマズイ書き方というものがあるのです。例えば、

#define SQUARE( x ) ( x * x )

といった書き方です。これはある数値の2乗の値を返すためのマクロで、一見すると良さそうに見えます。ですが、実際プログラムに組み込んでみるととんでもない事態が起こり得る場合があります。

#include <stdio.h>
#define SQUARE( x ) ( x * x )

int main( void ){

	int l;
	int m = 25;		//25
	int n = 15;		//15

	l = m + n;		//25 + 15 = 40

	//2乗を求める
	printf( "l^2 = %d\n",SQUARE( l ) );
	printf( "( m + n )^2 = %d\n",SQUARE( m + n ) );

	return 0;
}

上記のプログラムを、単純に頭で考えてみると、"l^2"は40*40=1600となり、"( m + n )^2"は( 25 + 15 )*( 25 + 15 ) = 40*40 = 1600となると思います。しかし、実際にコンパイルし実行すると、

l^2 = 1600
( m + n )^2 = 415

となってしまいます。"l^2"は1600ですが、"( m + n )^2"が415になっています。何故でしょうか?

流れ的にわかると思いますが、マクロが悪さをしているからなのです……というよりも、プログラムを書いた人自身がマクロのことを正しく理解していないために起こった、と言った方が正しいです。では、どうしてこのような結果になってしまったのでしょうか?

これは、置換をすることですぐわかります。

先のプログラムのSQUARE(x)部分を置換してみましょう。すると、

#include <stdio.h>

int main( void ){

	int l;
	int m = 25;		//25
	int n = 15;		//15

	l = m + n;		//25 + 15 = 40

	//2乗を求める
	printf( "l^2 = %d\n",( l * l ) );
	printf( "( m + n )^2 = %d\n",( m + n * m + n ) );

	return 0;
}

となりますね。もうお気づきになられたでしょうか?"( m + n * m + n )"となっていますね。これを計算していくと、足し算よりも掛け算を先にやらなければいけないので、m*nを実行し、そのあとにmとnを足すことになります。つまり、15 + 25 * 15 + 25 = 415となりますね。

このように、#defineはプログラマが意図していない動作を引き起こしてしまう可能性があるのです。じゃあどうすればいいのかというと、単純に括弧を付けてあげればいいのです。括弧内のほうが優先度が高いので、足し算や掛け算の順序をいちいち考える必要なんかありません。先程の2乗マクロは、

#define SQUARE(x) ( (x) * (x) )

と、してあげればいいわけです。

このように、マクロは便利である反面、とてもあやふやで危ないものでもあるのです。つまりなにが言いたいかというと、「括弧をいっぱいつけろ」ってことです。関数マクロの引数の部分は必ず括弧で括るようにしておけば、とりあえずは大丈夫かと思います。

AVR C言語 制御 その他
inserted by FC2 system