マクロの問題
出典: フリー百科事典『ウィキペディア(Wikipedia)』 (2020/10/02 16:40 UTC 版)
健全なマクロの機能を持たないプログラミング言語では、マクロの展開中に作成された変数の束縛によって、すでに存在する変数の束縛が隠されてしまう可能性がある。C言語では、次のようなコードによってこの問題を説明できる。 #define INCI(i) {int a=0; ++i;}int main(void){ int a = 0, b = 0; INCI(a); INCI(b); printf("a is now %d, b is now %d\n", a, b); return 0;} C言語のプリプロセッサで上のコードを変換すると、次のコードが生成される。 int main(void){ int a = 0, b = 0; {int a=0; ++a;}; {int a=0; ++b;}; printf("a is now %d, b is now %d\n", a, b); return 0;} トップのスコープで宣言された変数 a は、マクロの中にある変数 a によって隠されてしまっている。その結果、プログラムを実行しても変数の値は変化せず、コンパイルされたプログラムは次のように出力する。 a is now 0, b is now 1 この問題を解決する最も簡単な方法は、現在のプログラムに含まれるどんな変数とも衝突しない名前を、マクロ変数に与えることである。 #define INCI(i) {int INCIa=0; ++i;}int main(void){ int a = 0, b = 0; INCI(a); INCI(b); printf("a is now %d, b is now %d\n", a, b); return 0;} INCIa という名前の変数が作られないかぎり、この解決方法によって正しい出力が得られる。 a is now 1, b is now 1 現在のプログラムでは問題が解決されたが、この解決策は十分頑強であるとはいえない。プログラマは、マクロの内部で使用された変数と、残りのプログラムで使われる変数とを、常に衝突しないように気をつけなければならない。たとえば、変数 INCIa についてマクロ INCI を使おうとすれば、以前のマクロで変数 a について問題が起きたのと同じように問題が起こることになる。 「健全な問題」は、変数の束縛に拡張を加える。次のようなCommon Lispのマクロを考えてみる。 (defmacro my-unless (condition &body body) `(if (not ,condition) (progn ,@body))) このマクロには、変数への参照が含まれていないので、シンボル"if"、"not"、"progn" は、すべて普通の定義に束縛される。しかし、上のマクロが次のように使用された場合、 (flet ((not (x) x)) (my-unless t (format t "This should not be printed!"))) "not"の定義はローカルな定義に置き換えられ、my-unless の展開結果が変化する。(標準の関数やオペレータのグローバルまたはローカルな再定義は、ANSI Common Lispによれば、不確定な振る舞いを起こすとされている。そのため、上のような使い方をしても、実際には実装によってエラーと判断される可能性がある。) 一方、健全なマクロは、すべての識別子("if"や"not")のレキシカルスコープが自動的に保存される。この性質は、「参照透過性」と呼ばれている。 しかし、プログラム内で定義した関数は同じようには保護されていないので、次のようなコードでは問題が起こることがある。 (defmacro my-unless (condition &body body) `(if (user-defined-operator ,condition) (progn ,@body))) (flet ((user-defined-operator (x) x)) (my-unless t (format t "This should not be printed!"))) この問題のCommon Lispでの解決策は、パッケージを利用することである。 パッケージの中に置かれた my-unless マクロでは、user-defined-operator はそのパッケージ内のプライベートなシンボルである。そして、ユーザーコード内の user-defined-operator は異なるシンボルとなり、マクロ内のシンボルとは無関係なものになる。 しかし、健全なマクロを使用しているSchemeなどの言語は、マクロ展開の処理の一部として自動的に参照透過性を保証することで、識別子の誤捕捉を防いでいる。ただし、識別子を意図的に補足するために、健全なマクロのメカニズムを明示的に無視できるようにしている実装もある。 たとえば、次のSchemeによるmy-unless の実装は、期待通りの振る舞いをする。 (define-syntax my-unless (syntax-rules () [(_ condition body) (if (not condition) body (void))])) (let ([not (lambda (x) x)]) (my-unless #t (displayln "This should not be printed!")))
※この「マクロの問題」の解説は、「健全なマクロ」の解説の一部です。
「マクロの問題」を含む「健全なマクロ」の記事については、「健全なマクロ」の概要を参照ください。
- マクロの問題のページへのリンク