C言語のマクロ機能(define文)について基本的な使い方と利用例を示します。
C言語ではよく使う機能を関数化して使うことが多いですが、関数化する以外にも、マクロという機能を使ってよく使う処理や定数などを置き換える命令があります。
マクロの基本的な使い方
マクロの基本的な使い方を簡単な例で示します。
macro1.c
/* macro1.c マクロの基本 */
#include <stdio.h>
#define MAX 5 /* MAXを5と置き換える */
/* 置き換えるタイミングはコンパイルするとき */
int main(void)
{
int i;
for (i=0; i<MAX; i++){ /* for(i=0; i<5; i++){ と同じ意味になる */
printf("%d\n", i);
}
return 0;
}
実行結果
0
1
2
3
4
解説
C言語でマクロ定義するためには#define 文を用います。
#define MAX 5
マクロは単純な文字列置き換えを行います。上記マクロでは、 プログラム中に登場するMAX という文字列を 5 に置き換えてコンパイルしなさい、という意味になります。
使い方
#define 《置き換えを行う文字列》 《置き換え後の文字列》
《置き換えを行う文字列》と《置き換え後の文字列》の間は半角スペースまたはタブで区切る必要があります。
#define文は、#include文同様にプログラムの先頭に記述する必要があります。
上記例では main 関数の外側に記述していますが、main 関数内側の先頭に記述することも可能です。
#include <stdio.h>
int main(void)
{
#define MAX 5 /* MAXを5と置き換える */
int i;
:
:
マクロ定義による文字列置き換えはコンパイル前に実行されます。ですから先ほどのプログラムはコンパイル時には次のように MAX の部分を 5 に置き換えてからコンパイルすることになります。(厳密には #include <stdio.h>の部分も置き換えが発生していますが…)
/* コンパイル時のイメージ */
#include <stdio.h>
int main(void)
{
int i;
for (i=0; i<5; i++){
printf("%d\n", i);
}
return 0;
}
C言語では、先頭が # の行をプリプロセッサ命令といって、コンパイル前に処理されます。例えば #include <stdio.h>は、コンパイル前に #include <stdio.h> を記述した位置に stdio.h というファイルの中身が挿入されます。 #define も基本的な考え方は同じで、ユーザが指定した文字列を置き換えるようになっています。
ちなみに #define による単純な置き換え例として配列の要素数をマクロ宣言で置き換えて使うという利用方法もあります。
macro2.c
/* macro2.c マクロの基本 */
#include <stdio.h>
#define MAX 5
int main(void)
{
int i;
int suti[MAX];
int gokei = 0;
for (i=0; i<MAX; i++){
printf("数値%d : ", i+1);
scanf("%d", &suti[i]);
gokei += suti[i];
}
printf("合計 = %d\n", gokei);
return 0;
}
実行イメージ
数値1 : 10 [Enter] 数値2 : 20 [Enter] 数値3 : 30 [Enter] 数値4 : 40 [Enter] 数値5 : 50 [Enter] 合計 = 150
この方法であれば、#define MAX 5 の数値部分を変更するだけで配列の要素数とループの実行回数が一度に変更でき、プログラム入力ミスも少なくなります。
マクロの利用例
真と偽をTRUE/FALSEとして定義
次の例では、プログラムを見やすくするためにマクロ定義 TRUE と FALSE を使っています。
macro_true_false.c
/* 真と偽をTRUE/FALSEとしてマクロ定義 */
#include <stdio.h>
#define TRUE 1
#define FALSE 0
int check(int x, int y) /* 2つの数の一致をチェックする関数 */
{
if(x == y) return TRUE;
else return FALSE;
}
int main(void)
{
if(check(10, 11) == TRUE){
printf("一致\n");
}
else{
printf("不一致\n");
}
return 0;
}
実行結果
不一致
半径から円周の長さを求めるマクロ
引数を伴うような関数に近い使い方もマクロで可能です。
半径から円周の長さ(= 2 × 円周率 × 半径)を返してくれる計算式をマクロとして定義してみました。(ついでに円周率PIもマクロとして定義しています)
macro_enshu.c
/* 半径から円周の長さを求めるマクロ */
#include <stdio.h>
#define PI 3.14 /* 円周率 */
#define ENSHU(r) 2 * PI * r /* 円周の長さを求める */
int main(void)
{
double r;
printf("半径? ");
scanf("%lf", &r);
printf("円周の長さは %.1fです\n", ENSHU(r) );
return 0;
}
実行イメージ
半径? 10 [Enter]
円周の長さは 62.8です
このマクロのポイントは printf関数で使用した際、ENSHU(r) の r 部分は変数の内容が入っている点です。
イメージ(10 はキーボードから入力した値とする)
printf("円周の長さは %.1fです\n", ENSHU(10) );
マクロ作成時の注意点
マクロは単純な文字列置換を行います。単純なだけに一つ注意点があります。
次の指定した数の2乗を返すマクロを見てください。
macro_jijo1.c
/* 指定した数の2乗を返すマクロ */
#include <stdio.h>
#define JIJO(x) x * x
int main(void)
{
printf("%d\n", JIJO(5) );
return 0;
}
実行すると
25
特に問題はありません。
次にJIJOの引数を 4-2 に変更して実行してみます。
/* 指定した数値の2乗を返すマクロ */
#include <stdio.h>
#define JIJO(x) x * x
int main(void)
{
printf("%d\n", JIJO(4-2) );
return 0;
}
実行結果
-6
賢い人なら「問題なし」となると思いますが、わたしは計算結果に「4」を期待していました。
勝手に頭の中で次のような式を思い描いていたのです。
マクロの定義ではこうでした。
define JIJO(x) x * x
上記のマクロで、xが 4-2 の場合 4-2 * 4-2 という式に置き換えます。括弧を自動的に式につけることはありません。
注意しなければいけないのは計算の優先順位です。
掛け算(×)の方が引き算(-)より優先順位が高いので、実際の計算はこうなります。
この結果 -4 という表示になったわけです。
もし、4-2 のような値が設定された場合に (4-2) * (4-2) という式で計算したければ、
define JIJO(x) (x) * (x)
と定義する必要があります。
macro_jijo2.c
/* 指定した数値の2乗を返すマクロ(改良) */
#include <stdio.h>
#define JIJO(x) (x) * (x)
int main(void)
{
printf("%d\n", JIJO(4-2) );
return 0;
}
実行結果
4
マクロと関数の違いは?
C言語で関数を作ったことがある方なら、「マクロと関数の違いってあるの?」
と考えるかもしれません。
違いはあります。
そもそもマクロの中で計算を伴う場合、型チェックが厳密ではありません。
次のような割り算を行うマクロを定義したとします。
#define DIV(x) x/3
マクロの引数xに整数型(int)の変数nを入れて表示させてみます。
int n = 10; printf("%d\n", DIV(n) );
表示結果
3
次に変数型を実数型(double)にして実行してみます。
double n = 10.0; printf("%f\n", DIV(n) );
表示結果
3.333333
それなりに計算してくれます。
関数だとコンパイル時に変数型のチェックを行うため、そもそも実行ファイルが作成されませんが、マクロだとコンパイルが通り、実行出来てしまいます。厳密な変数型のチェックが必要な場合は関数で作成した方がいいでしょう。
以上、C言語のマクロ機能(define文)について基本的な使い方と利用例でした。
あなたならどんなマクロを作りますか?
コメント