C言語のマクロはとても便利ですが、落とし穴も多くあります。このページではマクロの注意点について説明します。
マクロ置換は、単純な文字列の置き換えです。このことを忘れていると、予想外の結果を生むことになります。
マクロの置換後の字句は、カッコで囲むほうがよいでしょう。プログラム中で使用された際に、置換後の字句の中に、前後にある演算子よりも優先度の低い演算子があった場合、予想と違う結果を生むことになるからです。
下記のプログラムでは、プログラムを書いた人の意図とは違う計算結果となります。これは「PRICE * num」が「COST + 10 * num」とマクロ展開され、COSTに10 * numの計算結果が足されるためです。
/**
* @file MacroAttention1_1.c
* @brief マクロの注意点.その1-1
*/
#include <stdio.h>
#include <stdlib.h>
#define COST 100
#define PRICE COST + 10
int main(int argc, char *argv[])
{
int num = 20;
int total;
total = PRICE * num;
printf("%d¥n", total);
return EXIT_SUCCESS;
}
この問題を回避するには、下記のようにします。この例では「PRICE * num」が「(COST + 10) * num」とマクロ展開され、意図したとおりの計算結果となります。マクロの置換後の字句は、カッコで囲むほうがよいでしょう。
/**
* @file MacroAttention1_2.c
* @brief マクロの注意点.その1-2
*/
#include <stdio.h>
#include <stdlib.h>
#define COST 100
#define PRICE (COST + 10)
int main(int argc, char *argv[])
{
int num = 20;
int total;
total = PRICE * num;
printf("%d¥n", total);
return EXIT_SUCCESS;
}
マクロの置換後の字句は、カッコで囲むほうがよいのですが、例外もあります。単一の識別子や関数呼び出しに展開されるマクロは、周囲の演算子の優先順位の影響を受けませんので、カッコをつける必要はありません。また、配列、構造体や共用体のメンバに展開されるマクロもカッコで括る必要はありません。例を挙げてみます。
#define PRICE calc_price()
#define NEXT_ITEM item->next
#define MEMBER_ID member.id
#define CURRENT_VALUE value[current]
関数形式マクロの置換後の字句の中にある引数にはカッコをつけたほうがよいでしょう。これも演算子の優先順位の関係により、計算の順番が変わる可能性があるためです。下記の例では、「CIRCLE_AREA(100 - 45)」が「(PI * 100 - 45 * 100 - 45)」に展開されます。その結果、意図しない計算結果となってしまいます。
/**
* @file MacroAttention2_1.c
* @brief マクロの注意点.その2-1
*/
#include <stdio.h>
#include <stdlib.h>
#define PI 3.14159265358979323846
#define CIRCLE_AREA(r) (PI * r * r)
int main(int argc, char *argv[])
{
double area;
area = CIRCLE_AREA(100 - 45);
printf("%f¥n", area);
return EXIT_SUCCESS;
}
この問題を回避するには、下記のようにします。この例では「CIRCLE_AREA(100 - 45)」が「(PI * (100 - 45) * (100 - 45))」とマクロ展開され、意図したとおりの計算結果となります。関数形式マクロの引数はカッコで囲むほうがよいでしょう。
/**
* @file MacroAttention2_2.c
* @brief マクロの注意点.その2-2
*/
#include <stdio.h>
#include <stdlib.h>
#define PI 3.14159265358979323846
#define CIRCLE_AREA(r) (PI * (r) * (r))
int main(int argc, char *argv[])
{
double area;
area = CIRCLE_AREA(100 - 45);
printf("%f¥n", area);
return EXIT_SUCCESS;
}
この注意点にも、例外があります。下記のように引数がコンマで囲まれている場合です。コンマは優先順位がどの演算子よりも低いのでカッコで括る必要はありません。
#define HOGE(x, y, z) HOGEHOGE(x, y, z)
また、##演算子をや#演算子を用いる場合、隣接する文字列を連結する場合などは、引数をカッコで括ってはいけません。
#define JOIN(x, y) (x ## y)
#define ERR_PRINT(x) fprintf(stderr, #x " = %d¥n", x)
関数形式マクロがコードに展開された後、引数が評価される回数は1回とは限りません。2回以上評価されるかもしれませんし、あるいは1回も評価されないかもしれません。そのため、マクロの引数に+=, -=などを使った代入、インクリメント、デクリメント等を使用した式を渡すと予想外の結果を生むことになります。
下記の例では、MAXマクロを「MAX(++a, --b)」と使用しています。使用者の意図は、aをインクリメントした値とbをデクリメントした値でより大きな値を得ることだと思われます。しかしMAX(++a, --b)は「((++a) > (--b) ? (++a) : (--b))」に展開されてしまうので、意図した結果となりません。
/**
* @file MacroAttention3_1.c
* @brief マクロの注意点.その3-1
*/
#include <stdio.h>
#include <stdlib.h>
#define MAX(x, y) ((x) > (y) ? (x) : (y))
int main(int argc, char *argv[])
{
int a = 1;
int b = 2;
printf("%d¥n", MAX(++a, --b));
return EXIT_SUCCESS;
}
マクロに渡す引数で注意しなければいけないのは、インクリメントやデクリメントだけではありません。他にも、関数呼び出しも引数で渡すと意図した結果とならないことが考えられます。
複数の文からなる関数形式マクロはdo-whileループで囲むとよいでしょう。次の例では、変数の宣言を可能とするために、マクロを波括弧で囲んでいます。しかし、使用箇所がif文の条件が真の場合に実行される部分であり、かつその部分が波括弧で包まれていないのでコンパイルエラーとなってしまいます。
/**
* @file MacroAttention4.c
* @brief マクロの注意点.その4
*/
#include <stdio.h>
#include <stdlib.h>
#define SWAP(type, x, y) { type tmp = x; x = y; y = tmp; }
int main(int argc, char *argv[])
{
int a = 1;
int b = 2;
double c = 3.3;
double d = 4.4;
if (argc < 2) {
return EXIT_SUCCESS;
}
if (atoi(argv[1])) SWAP(int, a, b);
else SWAP(double, c, d);
printf("a:%d b:%d c:%.1f d:%.1f¥n", a, b, c, d);
return EXIT_SUCCESS;
}
この問題は、次のようにマクロをdo-whileループで囲むことにより解決します。whileの条件が0なのでループの中は1回だけ実行されます。複数の文からなる関数形式マクロはdo-whileループで囲んだほうがよいでしょう。
#define SWAP(type, x, y) ¥
do { type tmp = x; x = y; y = tmp; } while (0)
変数や関数の型をオブジェクト形式マクロで定義することは避けた方がよいでしょう。マクロではポインタ型を正確に表現できないからです。下記の例では、str1はポインタ型ですが、str2はchar型となってしまいます。
#define STRING char *
STR str1, str2;
「STR str1, str2;」が「char * s1, s2;」と展開されるからです。型の定義は、下記のようにtypedefを使用した方がよいでしょう。
typedef char *STR;
STR str1, str2;
こうすれば、str1, str2ともにchar *型となります。
#演算子を使った下記のコードの場合、出力結果は「NUM」となり、100とはなりません。
#define STR(s) #s
#define NUM 100
printf("%s¥n", STR(NUM));
NUMの内容である100を表示させるためには、マクロを二段階にします。
#define STR2(s) #s
#define STR(s) STR2(s)
#define NUM 100
printf("%s¥n", STR(NUM));
マクロの最後にはセミコロンをつけないほうがよいでしょう。セミコロンをつけるかつけないかはマクロを呼び出す側の判断にすべきです。なぜならば、セミコロンの有無でプログラムの制御フローが変更されてしまう可能性があるからです。例えば次のコードの場合、問題が発生します。
#define INCREMENT(a, max) ((a) = ((a) + 1) % (max));
v = INCREMENT(i, 5) + 100;
INCREMENT()はaをカウントアップします。そして、もしmaxに達したらaを0にし、それを返します。上記コードの展開結果は次のようになります。
v = ((i) = ((i) + 1) % (5)); + 100;
INCREMENTマクロの最後にセミコロンがついているので、文が2つに別れてしまいます。しかも、コンパイルエラーになりません。原因の分かりにくい不具合となってしまいます。