【独学C言語入門⑳】マクロ定義について学ぼう【#define】

今回は《マクロ定義について学ぼう》というテーマで記事をまとめます。

「マクロ定義」を習得すると、より簡略化したプログラムコードを書くことができるようになります。しっかり学んでいきましょう!

尚、現在執筆中のC言語入門記事は以下の人を対象としています。

  • プログラミング未経験の人
  • C言語プログラミングの学習をこれから始めたい人
  • C言語プログラミングの学習を始めたばかりの人

マクロ定義について学ぼう

マクロ定義について学ぼう

マクロ定義とは

マクロ定義とはコンパイル時にプログラムコードの一部を置換する機能です。一般的に、数値や文字・文字列などの「定数」に別名を与えるときに使用します。

また、コンパイル時にプログラムコードを変換する機能全般のことを「プリプロセッサ」と呼びます。

主なプリプロセッサとして、次の3つが存在します。

  • マクロ定義
  • インクルード
  • 条件付きコンパイル

インクルードは既に使ったことがありますね(不安な方は<【独学C言語入門⑲】複数Cソースを管理しよう>を一読することをオススメします)。

条件付きコンパイルについては後日記事にまとめたいと思います。

マクロ定義の構文と使用例

兎にも角にも、まずはマクロ定義の構文と使用例について解説します。

【構文】

#define マクロ定義名    値

【使用例】

#define APPLE    (0)

※注意点

注意点として、定義のあとに『;』を記述しないというものがあります。

今までC言語を学んできた人がミスりやすい点ナンバーワンと言っても過言ではありません。

尚、うっかり『;』を記述してしまうと、コンパイルエラーとなる可能性があります。

【置換前】

#define MAX_APPLE_NUM       (10);    /* リンゴの最大個数 */

printf( "リンゴの最大個数は %d です\n", MAX_APPLE_NUM );

【置換後】

printf( "リンゴの最大個数は %d です\n", (10); );

上記のコードでは関数が『)』で閉じられる前に『;』が記述されている為、コンパイルエラーとなります。

マクロ定義の置換対象範囲

置換対象となるのは実際に処理されるプログラムコードそのものだけであり、文字列定数の中は置換されません。

【置換前】

#define MAX_APPLE_NUM       (10)    /* リンゴの最大個数 */

printf( "MAX_APPLE_NUM = %d\n", MAX_APPLE_NUM );

【置換後】

printf( "MAX_APPLE_NUM = %d\n", (10) );

マクロ定義のメリット

マクロ定義のメリットは次の2つです。

  • 可読性の向上
  • 保守性の向上

可読性とは

「可読性」とは、プログラムコードの読みやすさそのものです。

例えば、「リンゴ・ミカン・バナナのそれぞれの値段を格納する配列」を実装した場合を想定してください。

int prices[3];

prices[0] = 100;
prices[1] = 200;
prices[2] = 150;

この場合、配列の各要素がリンゴ・ミカン・バナナのいずれを指しているのかわからないですね。

それでは、添字をマクロ定義に書き換えてみましょう。

#define APPLE     (0)
#define ORANGE    (1)
#define BANANA    (2)

int prices[3];

prices[APPLE]  = 100;
prices[ORANGE] = 200;
prices[BANANA] = 150;

上記のコードであれば、『リンゴが100円、ミカンが200円、バナナが150円』という情報が配列に格納されていることがすぐわかりますね。

読みやすいコードのことを「可読性の高いコード」と言います。

保守性とは

「保守性」とは、プログラムコードの改善のしやすさのことです。

例えば、次のように「要素数が同じ配列」が記述されているコードを考えてみてください。

int array1[10];
int array2[10];
int array3[10];

仮に配列の要素数を 10 → 20 とする仕様変更があった場合、全ての配列の要素数を変える必要がありますね。

int array1[20];
int array2[20];
int array3[20];


では、配列の要素数をマクロ定義しておくとどうでしょうか?

まず、仕様変更前のコードは次のようになりますね。

#define MAX_ARRAY_NUM    (10)

int array1[MAX_ARRAY_NUM];
int array2[MAX_ARRAY_NUM];
int array3[MAX_ARRAY_NUM];

上記コードに対して、配列の要素数を 10 → 20 を行った場合は次のようになります。

#define MAX_ARRAY_NUM    (20)

int array1[MAX_ARRAY_NUM];
int array2[MAX_ARRAY_NUM];
int array3[MAX_ARRAY_NUM];

修正箇所は1つだけになりましたね。

このように改善しやすいコードのことを『保守性の高いコード』と呼びます。

ちなみに、同じ値であればなんでもかんでもマクロ定義で1まとめにしたほうがいいというワケではありません。

「同じ意味を持つ値」のみ1まとめにすることでコードの保守性は上がります。

逆に「違う意味を持つ値」まで1まとめにしてしまうと、かえってコードの保守性が下がってしまいます。

例えば、次のようなコードは保守性が下がってしまいます。

#define MAX_ARRAY_NUM       (10)

/* ユーザIDとパスワードは2つ1組 */
int userIDs[MAX_ARRAY_NUM];
int passwords[MAX_ARRAY_NUM];

/* 商品IDと商品の在庫は2つ1組 */
int productIDs[MAX_ARRAY_NUM];
int productNums[MAX_ARRAY_NUM];

本来、『ユーザID・パスワード』組と『商品ID・商品の在庫』組で要素数は異なる筈です。しかし、上記のコードでは「全ての配列の要素数は同じ要素数である」と誤解させかねません。

なので、仮に現時点での値が同じであっても、次のようにマクロ定義を行うのが最適と言えます。

#define MAX_USER_NUM       (10)
#define MAX_PRODUCT_NUM    (10)

/* ユーザIDとパスワードは2つ1組 */
int userIDs[MAX_USER_NUM];
int passwords[MAX_USER_NUM];

/* 商品IDと商品の在庫は2つ1組 */
int productIDs[MAX_PRODUCT_NUM];
int productNums[MAX_PRODUCT_NUM];

関数マクロ

マクロ定義は関数形式で定義することもできます。構文と使用例は次のようになります。

【構文】

#define 関数マクロ定義名(引数リスト)    式

【使用例】

#define add(a, b)    (a + b)

関数マクロには非常に重要な注意点が存在します。

関数マクロ定義名と『(』の間にスペースやタブを入れてはいけません

複数行に跨がるマクロ定義

マクロ定義は、行末に”\”を付与することで複数行に跨がって記述することができます。

【使用例】

#define calcurate(a, b, c)    {\
    int tmp = a;\
    tmp += b;\
    c = tmp;\
}

マクロ定義の副作用

一見、便利なマクロ定義ですが、実はある問題が発生する副作用が存在します。

それは、「思った通りの処理が行われない」という問題です。

勿論、この問題が発生するかどうかはマクロ定義の書き方によります。

それでは、次の2点について解説したいと思います。

  • 問題が発生する書き方
  • 問題が発生しない書き方

問題が発生する書き方

まずは実際のコードを見てみましょう。

#define APPLE_AND_ORANGE_NUM   10+15

double average = APPLE_AND_ORANGE_NUM/2;

一見、問題ないように見えますがマクロ定義を置換すると次のようなプログラムコードになります。

double average = 10+15/2;

するとどうでしょう。

15 / 2 が先に計算されてしまう為、計算結果は 17.5 となります。

本来想定していた計算結果は 12.5 なので、「思った通りの処理が行われなかった」ことになりますね。

問題が発生しない書き方

上記の書き方では、プログラムコードが置換されたことにより計算順が変わってしまうことが原因で「思った通りの処理が行われない」問題が発生していました。

『つまり、マクロ定義に計算式を書くのは危険ということ?』とお思いの方もいることでしょう。

そんなことはありません。次のように書けば、マクロ定義に計算式を書いても思った通りに処理が行われます。

#define APPLE_AND_ORANGE_NUM   (10+15)

double average = APPLE_AND_ORANGE_NUM/2;

上記コードを置換すると次のようになります。

double average = (10+15)/2;

このようなコードになれば、 10+15 が先に計算される為、計算結果は 12.5 となりますね。

基本的にマクロ定義には丸括弧をつける、これを覚えておいてください。

次回は「条件付きコンパイル」

今回は「マクロ定義」について学んでもらいました。可読性・保守性の向上には必須の機能なのでしっかり習得しておいてください。

次回は「条件付きコンパイル」について解説します。

「条件付きコンパイル」を習得すると、コンパイル時にプログラムの無効化・プログラムの切り替えなどができるようになります。強力な機能の1つです。

ご期待ください。

タイトルとURLをコピーしました