帶參宏的“文本替換”(今天的一個錯誤總結)

先看今天我寫的一段代碼:

debug.h

/* debug.h */
void CDECL DebugMsgBox(const TCHAR *szFormat, ...);

debug.c(可以忽略函數實現部分, 只要看到我使用了可變參數列表就行了)

/* debug.c */
void CDECL DebugMsgBox(const TCHAR *szFormat, ...)
{
    va_list pArgs;
    TCHAR szBuf[BUF_MAX_LEN];
    ZeroMemory(szBuf, sizeof(szBuf));
    va_start(pArgs, szFormat);
    _vsntprintf(szBuf, sizeof(szBuf) / sizeof(TCHAR), szFormat, pArgs); /* not  _vsnprintf */
    va_end(pArgs);
    MessageBox(NULL, szBuf, TEXT("Debug message:"), MB_OK); 
}
這裏我實現了一個函數(實際上是重用了《Windows程序設計》一書上的某個程序)用來輸出調試信息, 但是我既想利用可變參數的便利, 又想通過宏來控制DebugMsgBox的行爲, 於是我又在debug.h中加了如下代碼:

/* debug.h */
#define DEBUG_MODE

#ifdef  DEBUG_MODE
#define DEBUG_MSG(list) DebugMsgBox(list)
#else
#define DEBUG_MSG(list) ((void)0)
#endif
(當然, 這樣定義宏在VC++6.0下會產生一個警告:warning C4002: too many actual parameters for macro 'DEBUG_MSG'暫且不提)

然後我在輸出調試信息時通過使用:

DEBUG_MSG(TEXT("%s"), TEXT("Hello world!"))
來代替:

DebugMsgBox(TEXT("%s"), TEXT("Hello world!"))
    其實我是想通過 DEBUG_MODE來控制DEBUG_MSG的行爲:當我確實需要調試時, 我希望DEBUG_MSG(TEXT("%s"), TEXT("Hello world!"))相當於函數調用DebugMsgBox(TEXT("%s"), TEXT("Hello world!")),而不需要調試時, 我希望DEBUG_MSG(TEXT("%s"), TEXT("Hello world!"))相當於一個毫無作用的代碼行((void)0), 以減小運行時開銷。

    但是, 我的目的能達到嗎?

    不能。

    雖然宏是簡單的文本替換, 但是對於帶參數的宏, 它是嚴格按照參數個數來替換的。 看下面這段程序:

#include <stdio.h>

#define PRINT(list) printf(list)

int main(void)
{
	printf("Hello %d %s\n", 100, "world");
	PRINT("Hello %d %s\n", 100, "world");
	printf("Hello %d %s\n");
	return 0;
}
控制檯上會輸出什麼樣的結果呢?

Hello 100 world
Hello 0 (null)
Hello 0 (null)
Press any key to continue

    由此可見, 對於只帶一個參數的宏, 當你傳遞給它兩個參數時, 只有第一個參數被正確宏替換, 而第二個參數就被丟棄了。

希望別的小童鞋不要犯和我一樣的低級錯誤啊。

C99中倒是有參數個數可變的宏。

PS: 這是俺第一次看到printf輸出null。大笑

2011-10-28:

zotin大哥的評論:


其實還是有一種很不漂亮的解決辦法。
#define DEBUG_MSG(list) DebugMsgBox list
這兒的list不要用括號
使用宏的時候:
DEBUG_MSG( (TEXT("%s"), TEXT("Hello world!")) )
這兒多一層括號,就能展開成你想要的形式。

雖然多一層括號看起來很怪異,但至少能按想要的方式工作。

2011-11-08日07ware的評論:

#ifdef _MSC_VER
//Microsoft VC++
#define TRACE(fmt, ...) DebugMsgBox("%s, %s [Line %d]: " fmt, __FUNCTION__, __FILE__, __LINE__, __VA_ARGS__)

#else
//GCC on Linux & MacOS
#define TRACE(fmt, ...) DebugMsgBox("%s, %s [Line %d]: " fmt, __PRETTY_FUNCTION__, __FILE__, __LINE__, ## __VA_ARGS__)
#endif

Maybe VC6.0 is too old to support VA_ARGS, anyway all morden C++ compilers support my solution ;-)

Also, there is a simple optimization in your code:
TCHAR szBuf[BUF_MAX_LEN];
        ZeroMemory(szBuf, sizeof(szBuf));

==>
 TCHAR szBuf[BUF_MAX_LEN] ={0};

no need to call Windows API to zero a buffer.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章