プリプロセッサ
マクロ
C言語にはプリプロセッサ命令で、コンパイル時にコンパイラに命令を与えることができます
代表的なものが #include ですね
これは、C言語そのものではなく、プリプロセッサに対する命令です
これをプリプロセッサディレクティブと呼びます
もうひとつ、頻繁に使用されるであろうプリプロセッサディレクティブがマクロ置換です
マクロは定数に名前を与えるもので、C言語での開発効率を向上させます
マクロの定義は#defineディレクティブで行います
#define マクロ名 文字列
マクロ名にはマクロの名前を、文字列はそのマクロ名の内容を指定します
マクロはコンパイル時にソースコードに置き換えられます
これは内部フォーマットではないので、文字列と型はなんの関係もありません
マクロ名と文字列の間には、必ず1つ以上のスペースを入れ一行完結です
#include同様に、ステートメントではないのでセミコロンは必要ありません
マクロ名はC言語の変数の命令規則に従います
しかし、多くの場合マクロ名は変数と区別するため大文字にします
#include <stdio.h>
#define SAKURA "Sakura Kinomoto"
int main() {
printf("I'm %s" , SAKURA);
return 0;
}
比較的大規模なプログラムや、GUIプログラム(SDK MFC)になると多用することになるでしょう
多くのライブラリも、マクロによる定数を使用しています
#defineディレクティブは、C言語のブロックの影響を受けません
どの場所にでも定義することができ、一度定義すればそれ移行の全ての場所で使用できます
#include <stdio.h>
#define ARYMAX 5
int main() {
int ary[ARYMAX] , count;
for (count = 0 ; count < ARYMAX ; count++) {
#define DEF 10
ary[count] = DEF + count;
printf("%d " , ary[count]);
}
return 0;
}
DEF + count は一見すると変数同士のような算術ですが、意味は大きく違います
DEFはマクロなので、コンパイル時には 10 + count でコンパイルされています
ところで、一度定義したマクロはそれ以降永遠に有効です
これでは、必要のない部分にまでマクロが有効になってしまい
うっかり変数や関数名と衝突してしまうことも考えられます
このような事態を避けるために局所的マクロにすることができます
一度定義したマクロを、破棄するためのディレクティブで#undefというものがあります
#undef マクロ名
マクロ名が定義されていないものであれば、#undefはなんの意味も持ちません
定義されているマクロ名だった場合、そこでマクロ名を未定義にします
#include <stdio.h>
#define YUKI "Kitty on your lap\n"
int main() {
#ifdef YUKI
printf(YUKI);
#endif
#undef YUKI
#ifdef YUKI
printf(YUKI);
#else
printf("ひざの上の同居人");
#endif
return 0;
}
#undef YUKI によって、YUKIマクロの定義が未定義の状態になります
その結果、2回目の#ifdefで#elseが実行されるのを確認できます
マクロ関数
#defineには、もう少し高度な使用方法もあります
これは、マクロ関数と呼ばれ、プリプロセッサディレクティブには変わりありませんが
少しだけ関数的な処理ができるマクロです
マクロ関数も、やはり仮引数を指定して引数を受け取るとことができます
しかし、プリプロセッサディレクティブであるいじょう、動的なプログラムではありません
比較的簡易な計算処理をマクロ関数で定義すると開発効率を向上できます
マクロ関数の定義は、やはり #define を使用します
マクロ名に引数リストを指定することで、置き換える時に指定の定数を算術できます
#define マクロ名(引数リスト) 処理
たとえば、次のプログラムを見てください
#include <stdio.h>
#define ADD(m , n) m = m + n
int main() {
int a = 0;
ADD(a , 10);
printf("%d" , a);
return 0;
}
ADD()がマクロ関数です
これは、じっさいの関数のように値を渡したり参照しているわけではありません
コンパイル時にプリプロセッサによって次のように展開されるだけです
ADD(a , 10) -展開-> a = a + 10
グラフィックを扱うアプリケーションなどは、似たような計算式を多用します
何度も書きなおすと、いくら単純な計算式でも人為的なミスがおこります
そのようなことも、関数マクロを使用することで未然に防ぎ、開発効率の向上を図れます
ただし、関数マクロは算術されて展開されるわけではありません
展開後の算術優先順位で、予想しなかった計算順序になり正しい答えが得られないことがあります
このような理由から、関数マクロは括弧をつけて計算順序を保護するのが一般的です
#include <stdio.h>
#define ADD(m , n) ((m) + (n))
int main() {
int a;
a = ADD(2 * 5 , 9 / 3);
printf("%d" , a);
return 0;
}
組み込みマクロ
C言語には(すくなくとも、ANSI C標準には)組み込みマクロが用意されています
これは、最初から定義されているマクロです
組み込みマクロは、コンパイラによっては特有のマクロが追加されていたりするかもしれません
ここでは、ANSI C標準が定める組み込みマクロを紹介します
組み込みマクロで、代表的かつ実用的なのは次の4つです
- __LINE__ : 現在の行番号
- __FILE__ : コンパイルされているファイル名
- __DATE__ : コンパイル時の日付 ( 月/日/年 )
- __TIME__ : コンパイル時の時間 ( 時:分:秒 )
次のプログラムで、それを確認してみましょう
#include <stdio.h>
int main() {
printf("LINE = %d\n" , __LINE__);
printf("FILE = %s\n" , __FILE__);
printf("DATE = %s\n" , __DATE__);
printf("TIME = %s\n" , __TIME__);
return 0;
}
プロジェクトによっては、ソースファイルが何十個にも分かれたりします
ファイル名を表示させたり、最終コンパイル時の時間を表示させるなど
開発効率の向上に必要なこともあるかもしれません
何らかの理由で、ファイル名と行数を変えなくてはならない場合
行番号とファイル名の関連ずけを変更する#lineディレクティブがあります
#line 行番号 "ファイル名"
行番号とファイル名を指定することで、関連付けられている内容を変更できます
これは、実際に行番号が変わったり、ファイル名が変更されたりすることはありません
定数__LINE__と__FILE__の内容を変更するディレクティブです
関連付けられている内容を変更するので、コンパイラのエラーメッセージもそれに従います
#include <stdio.h>
#define YUKI "Kitty on your lap\n"
int main() {
printf("LINE = %d\n" , __LINE__);
printf("FILE = %s\n" , __FILE__);
#line 100 "love_hina"
printf("LINE = %d\n" , __LINE__);
printf("FILE = %s\n" , __FILE__);
return 0;
}
#define identifier token-stringopt
#defineは identifier 定義する
これが定義されると、それ移行にidentifierがソースに出てくるとtoken-stringoptに置き換えます
token-stringoptは省略可能です
その場合はidentifierが出てきても、展開時に削除されるだけですが
条件付きコンパイルなどのデバッグ用に定義します
#define identifier[( identifieropt, ... , identifieropt )] token-stringopt
identifieroptをtoken-string で使うと、実際の値に置き換える位置をマークできます
実引数の順序が複雑な場合は、正しく解釈されるように括弧を使用するのが一般的です
#undef identifier
identifierには、マクロ名を指定します
指定されたマクロの定義を削除します。パラメータ リストは指定しません
#line digit-sequence "filename"opt
digit-sequenceには、変更する行番号を
"filename"optにはファイル名を指定します(省略可能)
__LINE__や__FILE__定数の内容を変更することができます
自己言及的エラー メッセージをプログラム テキストに挿入できます