エンジ部ログ

エンジニアもすなるブログというものを、

C言語

[C言語] マクロ関数の使い方

はじめに

マクロ関数はとても使いやすく、わかりやすい仕組みです。この記事ではマクロ関数の使い方について解説します。一方、マクロ関数には様々な副作用も存在します。本記事の後半ではマクロ関数を使うことでのデメリットも併せて解説します。

マクロとは

マクロ関数を説明する前にまず、マクロについて考えます。

マクロは変数やconstを用いた定数とは異なり、コードの該当箇所を直接置き換えるものです。そのため、変数のように値を代入するわけではなく、データ型も持ちません。

マクロの使い方

#include<stdio.h>

#define NUM 100

int main(void){

  int a=5;

  a=a*NUM;
  printf("%d\n",a);

  return 0;
}

このプログラムでは、マクロNUMを数字の100と認識して実行しています。実行結果は以下の通りとなります。

>>500

関数形式マクロについて

関数形式マクロにおいても、基本的には一般的なマクロと同じく直接置き換えます。使い方は以下のようになります。

define 関数名(引数) (処理内容)

関数形式マクロでも、型を持たないため、この関数の引数に型を指定する必要はありませんし、戻り値をreturn句で返す必要もありません。

以下に関数形式マクロを用いたプログラム例を示します。

#include<stdio.h>

#define swap(a,b){a+=b;b=a-b;a-=b;}

int main(void){

  int a,b;

  a=20;
  b=30;

  printf("a = %d , b = %d\n",a,b);

  swap(a,b);

  printf("a = %d , b = %d\n",a,b);

  return 0;
}

この場合、出力は以下の通りとなります。

>>a = 20 , b = 30
>>a = 30 , b = 20

これは、以下のプログラムと同等の意味になります。

#include<stdio.h>


int main(void){

  int a,b;

  a=20;
  b=30;

  printf("a = %d , b = %d\n",a,b);

  {a+=b;b=a-b;a-=b;}
  
  printf("a = %d , b = %d\n",a,b);

  return 0;
}

マクロ関数のメリット

  • 型指定の必要がない
  • 関数呼び出しのオーバーヘッドがない

マクロ関数では型を指定しないため、型ごとに関数を用意する必要がありません。また、マクロはコンパイル前に前処理さるため、呼び出しのオーバーヘッドがなく、一般的な関数と比べて実行速度が速い場合があります。

マクロ関数のデメリット

  • 実行ファイルのサイズが大きくなる
  • 安易に使用するとバグの原因となる

一方マクロ関数には以上のようなデメリットが存在します。マクロ関数は単純なコードの置き換えを行うため、コンパイルする際にマクロ関数を展開します。そのため、実行ファイルのサイズが大きくなってしまい、利用するデータ領域が多くなります。さらには、様々な注意点があり、安易な使用はバグの原因となってしまいます。

ここからは、バグが発生する例を使って詳しく見ていきましょう。

バグ発生例1:かっこを付けないと・・・

#include<stdio.h>

#define multi(a,b)(a*b)

int main(void){

  int a,b;

  a=3;
  b=2;

  printf("%d\n",multi(4+a,b+1));

  return 0;
}

この場合、出力は以下のようになります。

>>11

これは、マクロが単純なコードの書き換えであることから生じます。一般的に、上記のコードは(4+a)×(b+1)の計算を想定しています。しかし、上記のマクロ関数部では、掛け算の前後のa,bにかっこが無いため、(4+a*b+1)という式になってしまいます。こうすると、今回は(4+3*2+1)となってしまうので2*3の部分が先に計算されてしまい、計算が想定外の結果となってしまいます。

これを改善するためには、マクロ関数部にかっこを付け、以下のようなコードにする必要があります。

#include<stdio.h>

#define multi(a,b)((a)*(b))

int main(void){

  int a,b;

  a=3;
  b=2;

  printf("%d\n",multi(4+a,b+1));

  return 0;
}

バグ発生例2:引数は値がコピーされるわけではない

#include<stdio.h>

#define pow(a)((a)*(a))

int main(void){

  int a;

  a=2;

  printf("%d\n",pow(a++));
  printf("%d\n",pow(a++));

  return 0;
}

この場合、出力は以下の通りとなります。

>>6
>>20

これは、マクロ関数の引数は値がコピーされるわけではないことに起因します。一般的な関数での引数の値は、値そのものをコピーされて使われます。しかし、マクロ関数では直接コードが挿入されるだけなので、引数が呼び出されるごとに引数内の計算も実行されてしまいます。その結果、上記コードでは2*3と4*5の計算結果がそれぞれ出力されるという流れになりました。

こういった問題から、関数形式マクロよりもインライン関数やスタティック関数を使うことも呼びかけられています。

最後に

今回はマクロ関数の使い方について解説しました。メリットやデメリットが様々存在します。状況に応じて、使い分けを行う必要がありそうです。コードもわかりやすく扱いやすいので、使われる場面も多いかと思います。ただ、バグが発生した場合の対処も難しいため、自分は今後インライン関数の利用も検討しようかと思います。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA