この記事の要点
- defineを使って、マクロでの置換ルールを定義する
- マクロ定数を定義することで、記述や修正が楽になる上、わかりやすいコードになる
- 関数マクロでは型チェックされないため、どんな型の引数でも受け取れる
以前、C言語とはどのようなプログラミング言語なのか、基礎についてご紹介していました。
今回は、C言語のdefine(マクロ)についての解説です。
defineを使うと、マクロを定義できます。
うまくマクロを使いこなすと、楽に記述でき、しかもわかりやすいコードになります。
ただし、型チェックされないなどデメリットもあるので、正しい理解が必要です。
当記事を読めば、defineの使い方や、マクロ定数、関数マクロの具体的な利用シーンがわかります。
C言語のdefineとは
C言語には、あらかじめ決めておいたルールにしたがって、コンパイル前にソースコード上の文字列を置換してくれる機能があります。
これをマクロと呼びます。
この、マクロでの置換ルールを定義する命令がdefineです。
defineは、マクロ定数の定義や、関数マクロの定義によく使われます。
defineを使うメリット
defineを使うメリットは、以下の通りです。
- あちこちで繰り返し使う値や処理の、記述や修正が楽になる
- マジックナンバーを避けて、ソースコードの可読性が上がる
- 関数マクロは、通常の関数より実行時の処理が高速
- 関数マクロは、コンパイル時に型チェックされないので、引数にどんな型でも渡せる
defineを使うデメリット
defineを使うデメリットは、以下の通りです。
- 多用すると、ソースコードのサイズが大きくなりコンパイルが遅くなる
- 関数マクロは、コンパイル時に型チェックされないので、実行時までミスに気づけない
defineの使い方
defineの構文は、以下の通りです。
#define 文字列1 文字列2
「文字列1」に記述した文字列を、「文字列2」に記述した文字列に置換します。
言葉で説明しても伝わりにくいと思いますので、実際のサンプルコードを見ていきましょう。
#include <stdio.h> #define ARRAY_SIZE 5 int main(void){ int array[ARRAY_SIZE] = {1, 2, 3, 4, 5}; for(int i = 0; i < ARRAY_SIZE; i++){ printf("%d ", array[i]); } return 0; }
このコードでは、配列の大きさ(=5)をdefineでマクロ化しています。
したがって、コンパイル前の処理(プリプロセッサ)で以下のように置換されます。
#include <stdio.h> int main(void){ int array[5] = {1, 2, 3, 4, 5}; for(int i = 0; i < 5; i++){ printf("%d ", array[i]); } return 0; }
このサンプルだけだと、defineを使うとどんなメリットがあるのか、想像がつきにくいかと思います。
ここからは、マクロ定数、関数マクロのそれぞれについて、具体的な利用シーンを掘り下げて解説していきます。
マクロ定数の利用シーン
マクロ定数とは、サンプルで示した、
#define ARRAY_SIZE 5
のような使い方のことです。
定数を使うメリットの1つは、マジックナンバーを避けることで、何をしているかわかりやすくなることです。
例えば、数百行を超える長いソースコードを解析しているシーンを思い浮かべてください。
その途中、突如次のような処理が目に入りました。
for(int i = 0; i < 5; i++){ int num = array[i]; // 処理が続いていく }
for文が5回繰り返されるのはわかりますが、なぜ5回なのかすぐにはわかりません。
配列の先頭5つに特別な値が入っているのか?あるいは何かの理由で5回だけ処理したいのか?
謎を解明するには、他の箇所も注意深く解析して情報を集める必要があります。
このように、ソースコードを書いた人にしかわからない謎の5という数字がマジックナンバーです。
では、もしこの5という数字が適切な命名で定数化されていたらどうでしょう。
for(int i = 0; i < ARRAY_SIZE; i++){ int num = array[i]; // 処理が続いていく }
これなら、配列の要素数分が5で、その数だけ処理を繰り返したいのだということが、他の場所を読まなくてもすぐに伝わります。
また、他にも定数には、繰り返し使う値の記述や修正が楽になるというメリットがあります。
例えば、「配列の要素数が5では足りなくなったので10に増やしたい」という場合に、定数化されていなければ、「5」と書かれている箇所を全てチェックして、関係するところだけを修正しなければなりません。
一方、定数化してあれば、defineによる定義箇所だけを修正するだけで作業完了です。
関数マクロの利用シーン
関数マクロとは、文字列をなんらかの処理に置換するマクロです。
様々な箇所で繰り返し使われる処理や、どんな型の引数でも受け取れる関数を定義したい場合に利用します。
例えば、配列の要素数を取得する処理はよく使うので、関数マクロとして定義されることがあります。
#define SIZEOF_ARRAY(array) sizeof(array) / sizeof(array[0]) 利用する際は、次のようにします。 #define SIZEOF_ARRAY(array) sizeof(array) / sizeof(array[0]) int main(void){ int array[10] = {}; printf("%d", SIZEOF_ARRAY(array)); // 「10」が出力される return 0; }
通常の関数と違い、SIZEOF_ARRAYの引数に型を指定していない点に注目してください。
これは、マクロは単なる文字列置換で、型チェックが行われないからです。
これによって、int配列でも、double配列でも、char配列でも気にせずに要素数を取得できます。
ただし、型チェックされない点はデメリットでもあります。
例えば、SIZEOF_ARRAYの引数に配列以外を渡したとしても、型チェックされないのでコンパイルエラーになりません。
つまり、プログラムを実行してみるまでエラーになりません(最悪の場合、実行時エラーにすらならない)。
そのため、関数マクロを定義する際は、一緒に使い方をコメントしておくなどの配慮が必要です。
まとめ
業務などで扱うソースコードでは、高確率でdefineが使われているでしょう。
defineをうまく使えば、読みやすくて保守性の高いコーディングができます。
一方で、型チェックされない、コンパイルが遅くなるといったデメリットもあるので、使いこなすにはある程度練習が必要です。
業務や課題などに活かせるよう、当記事を参考にdefineを使いこなす練習をしていきましょう。
また、C言語の初心者がつまずきやすいのが「ポインタ」です。
ポインタはC言語で必要不可欠な重要テクニックなので、しっかり理解する方法をご紹介しています。