1. 程式人生 > >C++中的巨集替換

C++中的巨集替換

1.引子
#define cat(x, y) x ## y
那麼cat(a, b)和cat(cat(a, b), c)的結果是啥.

#define str_impl(x) #x
#define str(x) str_impl(x)
的意圖何在.

2.規則
巨集替換是C/C++的預處理中的一部分,在C++標準中有4條規則來定義替換.

規則1:實參替換.
本條規則描述帶引數的巨集的替換過程.

對於巨集定義中的形參,在替換列表中,如果不是作為#或##的運算元,那麼將對應實參完全
展開(相當於對實參進行求值),然後將替換列表中的形參替換掉.如果是#或##的運算元,
那麼不進行替換.

規則2:多次掃描.

在所有的形參替換為實參後,對結果進行再次掃描,如果發現還有可替換的巨集,則進行替換,
否則中止.

規則3:遞迴替換抑制.

如果在替換列表中發現當前正在展開的巨集的名字,那麼這裡不進行替換.更進一步,在巢狀
的替換過程中發現已經替換過的巨集的名字,則不進行替換.

規則4:遞迴預處理抑制.

如果替換後的結果形成預處理指令,則不執行這條預處理指令.

3.例項
#define cat(x, y) x ## y

在cat(cat(a, b), c)中,首先掃描替換列表,發現x和y兩個形參作為##的運算元,那麼直接
將實參不作任何處理地搬過來,並進行連線運算,得到結果是cat(a, b)c

若在此基礎上增加
#define xcat(x, y) cat(x, y)
在xcat(xcat(a, b), c)中首先對x進行求值,也就是先計算xcat(a, b)的結果,容易得到
值是ab.然後對y進行求值,發現不必進行任何處理,值是c,得到結果cat(ab, c).
然後應用規則2,對cat(ab, c)進行求值,容易得到結果是abc.

顯然
#define str_impl(x) #x
#define str(x) str_impl(x)
的意圖在於防止#阻止巨集作為引數的時候被規則1阻止展開.

再看幾個C++標準中的例子:
#define x 3
#define f(a) f(x * (a))
#undef x
#define x 2
#define g f
#define z z[0]
#define h g(~
#define m(a) a(w)
#define w 0,1
#define t(a) a
f(y+1) + f(f(z)) % t(t(g)(0) + t)(1);
g(x+(3,4)-w) | h 5) & m(f)^m(m);
其結果分別是
f(2 * (y+1)) + f(2 * (f(2 * (z[0])))) % f(2 * (0)) + t(1);
f(2 * (2+(3,4)-0,1)) | f(2 * ( ~ 5)) & f(2 * (0,1))^m(0,1);

對於第一個,主要在於t(t(g)(0) + t)(1)的展開.
容易計算出最外層的t的實參是f(2 * (0)) + t,而作為t的引數傳入時其中的t是
正在被展開的巨集,所以根據規則3,不對這個t進行處理,保持不變,得到f(2 * (0)) + t(1).

對於第二個,h 5)被替換為g(~5),應用規則2,被替換為f(2 * ( ~ 5)).
而m(m)首先被替換為m(w),然後應用規則2再次進行替換,但是m已經是替換過的了,所以保持
不變,只對w進行替換.

#define str(s) # s
#define xstr(s) str(s)
#define debug(s, t) printf("x" # s "= %d, x" # t "= %s", \
x ## s, x ## t)
#define INCFILE(n) vers ## n /* from previous #include example */
#define glue(a, b) a ## b
#define xglue(a, b) glue(a, b)
#define HIGHLOW "hello"
#define LOW LOW ", world"
debug(1, 2);
fputs(str(strncmp("abc\0d", "abc", ’\4’) /* this goes away */
== 0) str(: @\n), s);
#include xstr(INCFILE(2).h)
glue(HIGH, LOW);
xglue(HIGH, LOW)

其結果分別是
printf("x" "1" "= %d, x" "2" "= %s", x1, x2);
fputs("strncmp(\"abc\\0d\", \"abc\", ’\\4’) = = 0" ": @\n", s);
#include "vers2.h"
"hello";
"hello" ", world"

關鍵是glue和xglue.
對於glue(HIGH, LOW);,首先有一個規則1的抑制,得到HIGHLOW;的結果,然後二次掃描,得到
"hello";
對於xglue(HIGH, LOW)沒有抑制效果,所以對引數求值,分別得到HIGH和LOW ", world",即
glue(HIGH, LOW ", world")
然後進行連線操作得到HIGHLOW ", world",最後再掃描一次得到"hello" ", world"

如果考慮字串的自然的連線,就可以得到"hello, world"了.