最近 survey 了一些 debug sink 實作,其中最原始的應該是 dprintf。用函數實作的dprintf有一些缺點,例如 releas build 時編譯器仍然無法去除額外的函式呼叫。另一方面我們希望能夠自動收集 __FILE__、__LINE__ 的資訊,節省機械化的打字。

要解決這個問題其實很簡單,這裡提供一種做法

void dprintf_impl(const char* fmt, ...);

#ifdef NDEBUG
# define DPRINTF (1)? (void) 0 : dprintf_impl
// alt. if (1) {} else dprintf_impl
#else
# define DPRINTF dprintf_impl("%s(%d): ", __FILE__, __LINE__);\
dprintf_impl
#endif

DPRINTF("gamma = %d \n", 123);

關鍵在於把 dprintf_impl 放在絕對不會被執行到的路徑上,讓 dprintf_impl 不會被編出來,由於這是非常基本的最佳化,沒有編譯器膽敢不做。就算在最差的情況下編譯器把整段照原意編出來,由於 dprintf_impl 這段不可能會被執行,所有參數 evaluation 的副作用也絕對不會發生,頂多是浪費一個分支指令,況且這個分支行為超級容易預測。至於要使用if或者?:則依照使用需求,i.e.要將此當成獨立陳述或者是expression而定。

不過如果我們對此不放心,還是想把所有個控制權交到 macro手上呢?若用 macro 實作也會有一些問題,主要是因為有些編譯器的 preprocessor 不接受不定長度參數。對 C99 相容或者是內建 __VA_ARGS__ extension 之編譯器這些都不是大問題,不過對於舊式編譯器卻沒有好的解決方法。

所以我想到了一個連古董級的 Turbo C 都可以吃的方法,實用性或許有待討論,就當成一個有趣的腦力遊戲吧。

宣告的部分。這裡只簡單呈現概念,很多 dprintf 應該要有的東西都被我去掉了。

const char ARG1 = '\n', ARG2 = '\n';

#define ARG1(ag) ag, ARG2
#define ARG2(ag) ag, ARG1

#define DPRINTF(format, ag) printf(format "%c", ARG1 ag)

使用的部分

double pi = 3.14159;
int x = 3;
DPRINTF("%s \npi = %f; x = %d;\n", ("hello")(pi)(x) );

語法看起來似乎有些古怪,不過確實可以 work。在這裡我用了一些非常有趣的性質,不過並沒有甚麼東西是超出規範的,就算回到 20 年前的 Turbo C 仍然支援這些特性。

最主要的概念是我們可以靠兩個 macro 互相展開,如同遞迴呼叫一般。然後我在前面定義了兩個字元常數刻意和 macro 撞名,如此一來,若是名稱後面有小括號就會被視為 macro;若是名稱後面沒有小括號就會被當成一般程式碼展開,由於展開之後也只是一個常數,並不會帶來副作用。

理論上可變長度參數函數並不會在乎你多餵一兩個參數,反正多餘的參數不會被處理,退出函數時還是會自動被 pop 掉。不過我用gcc編譯卻得到一個 warning,指出 format 字串和後續的參數列不符,這也沒甚麼大不了,我直接在format後面接個 "%c" 吃下最後的字元即可,其實不接"%c"也不會造成任何錯誤。


2010.11.23 補充: 畢竟最後插入的額外字元總是讓人非常不自在,所以我想到了一個展開的方法,雖然我用手邊幾種編譯器測試都沒有問題,但仍然不太確定這是不是合法的寫法。後來在 Boost.Preprocessor 也看到他使用非常類似的想法,所以我猜大概也難有其他的實作方式了。不過 Boost.Preprocessor 針對各種不同編譯器做了細節的調整,這是我所沒辦法達到的。

#define COUNT0(a) COUNT1
#define COUNT1(a) COUNT2
#define COUNT2(a) COUNT3
#define COUNT3(a) COUNT4
#define COUNT4(a) COUNT5
#define COUNT5(a) COUNT6

#define EXPAND_COUNT1(a) a
#define EXPAND_COUNT2(a) a, EXPAND_COUNT1
#define EXPAND_COUNT3(a) a, EXPAND_COUNT2
#define EXPAND_COUNT4(a) a, EXPAND_COUNT3
#define EXPAND_COUNT5(a) a, EXPAND_COUNT4
#define EXPAND_COUNT6(a) a, EXPAND_COUNT5

#define ENUM_SEQ_II(res) EXPAND_##res
#define ENUM_SEQ_I(res) ENUM_SEQ_II(res)
#define ENUM_SEQ(seq) ENUM_SEQ_I(COUNT0 seq) seq

#define DPRINTF(fmt, args) printf(fmt, ENUM_SEQ(args))

// Usage:
// DPRINTF("abc %d %f %s \n", (10)(23.5)("DO RE MI"));

novus 發表在 痞客邦 PIXNET 留言(0) 人氣()