C/C++ 中巨集與預處理使用方法大全 (VC)
C/C++ 中的巨集 (#define) 與預處理 (#if/#ifdef/#pragma) 的使用方法大全、使用技巧
開發環境:VC 2005
關鍵字:巨集, 預定義巨集, 預處理, 預編譯頭, VC, #pragma, 編譯選項, 程式區段
RTFM: Read The F__king Manual/MSDN
C/C++ 預定義巨集^
__LINE__: 當前原始檔的行號,整數
__FILE__: 當前原始檔名,char 字串,使用 /FC 選項產生全路徑
__DATE__: 當前編譯日期,char 字串,格式 Aug 28 2011
__TIME__: 當前編譯時間,char 字串,格式 06:43:59
__STDC__
__TIMESTAMP__: 最後一次修改當前檔案的時間戳,char 字串,格式 Sun Aug 28 06:43:57 2011
__cplusplus: 以 C++ 方式而非 C 語言方式編譯時定義,VC 2005 中定義為 199711L,配合 #ifdef 使用
例子:C/C++ 預定義巨集的取值^
- // MacroTest.h
- void PrintSourceInfo()
- {
- const _TCHAR* pszstdc;
- const _TCHAR* pszcpp;
- #if __STDC__
- pszstdc = _T("YES");
- #else
- pszstdc = _T("NO");
- #endif
- #ifdef __cplusplus
- pszcpp = _T("YES");
- #else
- pszcpp = _T("NO");
- #endif
- _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s, ANSI/ISO C: %s, C++: %s\n"),
- _T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__), pszstdc, pszcpp);
- }
- // 巨集化的 PrintSourceInfo()
- #define PRINT_SOURCE_INFO() \
- _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s\n"), \
- _T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__));
// MacroTest.h
void PrintSourceInfo()
{
const _TCHAR* pszstdc;
const _TCHAR* pszcpp;
#if __STDC__
pszstdc = _T("YES");
#else
pszstdc = _T("NO");
#endif
#ifdef __cplusplus
pszcpp = _T("YES");
#else
pszcpp = _T("NO");
#endif
_tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s, ANSI/ISO C: %s, C++: %s\n"),
_T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__), pszstdc, pszcpp);
}
// 巨集化的 PrintSourceInfo()
#define PRINT_SOURCE_INFO() \
_tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s\n"), \
_T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__));
MacroTest.h 中定義函式 PrintSourceInfo() 和 PRINT_SOURCE_INFO(),在 MacroTest.cpp include=> MacroTest.h,並呼叫它們
輸出結果:
(1). 使用函式 PrintSourceInfo(),無論 Debug/Release 方式編譯,無論是否 inline 化 PrintSourceInfo(),輸出結果相同,均是 MacroTest.h 的資訊:
File: d:\source\macrotest\macrotest.h, Line: 64, Date: Aug 28 2011, Time: 06:43:59, Timestamp: Sun Aug 28 06:43:57 2011, ANSI/ISO C: NO, C++: YES
(2). 使用巨集 PRINT_SOURCE_INFO(),Debug/Release 方式編譯輸出結果大致相同,均是 MacroTest.cpp 的資訊,只是 Debug 輸出的 __FILE__ 是全路徑,而 Release 輸出的是相對路徑:
File: d:\source\macrotest\macrotest.cpp, Line: 14, Date: Aug 28 2011, Time: 07:42:30, Timestamp: Sun Aug 28 07:38:25 2011
說明:
(1). __FILE__、__DATE__、__TIME__ 是 char 字串,而不是 wchar_t 寬字元字串,需配合 _T()、_t 系列函式使用
(2). 如果在函式 PrintSourceInfo() 中使用巨集,則 __FILE__、__LINE__、__TIME__ 等表示的是 PrintSourceInfo() 所在檔案,即例 1 中的 MacroTest.h 的資訊;如果在巨集 PRINT_SOURCE_INFO() 中使用巨集,因為巨集 PRINT_SOURCE_INFO() 巢狀展開的緣故,__FILE__ 等表示的是 PRINT_SOURCE_INFO() 展開所在檔案,即 MacroTest.cpp 的資訊
(3). 無論使用 PrintSourceInfo() 還是 PRINT_SOURCE_INFO(),__LINE__ 總是檔案 .h/.cpp 的固有行號,而非 [MacroTest.cpp include=> MacroTest.h] 預處理展開後的行號
(4). 在 VC 2005 中,上述編譯方式下沒有定義 __STDC__,要使 __STDC__ = 1,應同時滿足以下條件:
- (a). 以 C 方式編譯
- (b). 使用編譯選項 /Za,表示禁止 Microsoft C/C++ 語言擴充套件,從而相容 ANSI C/C++
C/C++ 預定義巨集用途:診斷與除錯輸出^
參考 VC CRT 和 MFC 的程式碼,注意:需要在巨集中使用 __FILE__、__LINE__,原因見上面“說明 (2)”
CRT 的診斷與除錯輸出:assert, _ASSERT/_ASSERTE, _RPTn/_RPTFn/_RPTWn/_RPTFWn^
CRT 的診斷巨集 assert()、_ASSERT()/_ASSERTE()
- // assert.h
- _CRTIMP void __cdecl _wassert(__in_z constwchar_t * _Message, __in_z constwchar_t *_File, __in unsigned _Line);
- #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
- // crtdbg.h
- #define _ASSERT_EXPR(expr, msg) \
- (void) ((!!(expr)) || \
- (1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \
- (_CrtDbgBreak(), 0))
- #ifndef _ASSERT
- #define _ASSERT(expr) _ASSERT_EXPR((expr), NULL)
- #endif
- #ifndef _ASSERTE
- #define _ASSERTE(expr) _ASSERT_EXPR((expr), _CRT_WIDE(#expr))
- #endif
// assert.h
_CRTIMP void __cdecl _wassert(__in_z const wchar_t * _Message, __in_z const wchar_t *_File, __in unsigned _Line);
#define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
// crtdbg.h
#define _ASSERT_EXPR(expr, msg) \
(void) ((!!(expr)) || \
(1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \
(_CrtDbgBreak(), 0))
#ifndef _ASSERT
#define _ASSERT(expr) _ASSERT_EXPR((expr), NULL)
#endif
#ifndef _ASSERTE
#define _ASSERTE(expr) _ASSERT_EXPR((expr), _CRT_WIDE(#expr))
#endif
CRT 的除錯輸出巨集 _RPTn()/_RPTFn(),n: 0 ~ 5
_RPTWn()/_RPTFWn() 是寬字元版
- // crtdbg.h
- #define _RPT_BASE(args) \
- (void) ((1 != _CrtDbgReport args) || \
- (_CrtDbgBreak(), 0))
- #define _RPTF0(rptno, msg) \
- _RPT_BASE((rptno, __FILE__, __LINE__, NULL, "%s", msg))
// crtdbg.h
#define _RPT_BASE(args) \
(void) ((1 != _CrtDbgReport args) || \
(_CrtDbgBreak(), 0))
#define _RPTF0(rptno, msg) \
_RPT_BASE((rptno, __FILE__, __LINE__, NULL, "%s", msg))
MFC 的診斷與除錯輸出:ASSERT/VERIFY, ASSERT_VALID, TRACE/TRACEn^
MFC 的診斷巨集 ASSERT()/VERIFY()、ASSERT_VALID()
- // afx.h
- #define ASSERT(f) DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0)))
- #define ASSERT_VALID(pOb) DEBUG_ONLY((::AfxAssertValidObject(pOb, THIS_FILE, __LINE__)))
// afx.h
#define ASSERT(f) DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0)))
#define ASSERT_VALID(pOb) DEBUG_ONLY((::AfxAssertValidObject(pOb, THIS_FILE, __LINE__)))
MFC 的除錯輸出巨集 TRACE()/TRACEn(),n: 0 ~ 3
- // atltrace.h
- #ifndef ATLTRACE
- #define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)
- #define ATLTRACE2 ATLTRACE
- #endif
- // afx.h
- #include <atltrace.h>
- #define TRACE ATLTRACE
- #define THIS_FILE __FILE__
- #define VERIFY(f) ASSERT(f)
- #define DEBUG_ONLY(f) (f)
- #define TRACE0(sz) TRACE(_T("%s"), _T(sz))
- #define TRACE1(sz, p1) TRACE(_T(sz), p1)
- #define TRACE2(sz, p1, p2) TRACE(_T(sz), p1, p2)
- #define TRACE3(sz, p1, p2, p3) TRACE(_T(sz), p1, p2, p3)
// atltrace.h
#ifndef ATLTRACE
#define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)
#define ATLTRACE2 ATLTRACE
#endif
// afx.h
#include <atltrace.h>
#define TRACE ATLTRACE
#define THIS_FILE __FILE__
#define VERIFY(f) ASSERT(f)
#define DEBUG_ONLY(f) (f)
#define TRACE0(sz) TRACE(_T("%s"), _T(sz))
#define TRACE1(sz, p1) TRACE(_T(sz), p1)
#define TRACE2(sz, p1, p2) TRACE(_T(sz), p1, p2)
#define TRACE3(sz, p1, p2, p3) TRACE(_T(sz), p1, p2, p3)
MFC 的除錯版 new^
- // afx.h
- void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
- #define DEBUG_NEW new(THIS_FILE, __LINE__)
- // 使用者程式碼
- // 除錯版 new
- #ifdef _DEBUG
- #define new DEBUG_NEW
- #endif
// afx.h
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)
// 使用者程式碼
// 除錯版 new
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
CRT 和 C 標準庫中的巨集^
NULL 空指標^
NULL 在 stddef.h, stdio.h, stdlib.h 等多個頭檔案中定義,是地址/指標型別的 0,如下:
- #ifdef __cplusplus
- #define NULL 0
- #else
- #define NULL ((void *)0)
- #endif
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
C++ 中的 0 是型別自動的,所以用 0 定義 NULL;而 C 中 0 是確定的 int 型別,所以需要強制
C++ 中,當 NULL 的相關運算元,如:對比操作 ptr == NULL,或函式的形參是指標型別時,或者能夠“從指標型別隱式轉換”時,0 被自動轉換為指標型別
例子:NULL 隱式轉換和 0 是型別自動的^
- // baby pointer wrapper
- class Pointer
- {
- public:
- // 非 explicit 建構函式,說明 Pointer 可以從指標型別 void* 隱式轉換
- Pointer(void* p) : m_Ptr(p)
- {}
- bool IsNull() const
- {
- return (m_Ptr == NULL);
- }
- private:
- void* m_Ptr;
- };
- // 形參可以從指標型別 void* 隱式轉換
- void TestPointer(Pointer ptr)
- {
- _tprintf(_T("ptr is %sNULL\n"), ptr.IsNull() ? _T("") : _T("NOT "));
- }
- // 使用者程式碼
- TestPointer(0); // OK,0 是型別自動的,0 被自動轉換為 void*,再次隱式轉換為 Pointer
- TestPointer(NULL); // OK,NULL 就是 0,同上
- TestPointer(1); // Error,C++ 中 1 不同於 0,它是確定的 int 型別,
- // 只能提升轉換到 float/double 型別,不能自動轉換為指標
- TestPointer((int*)1); // OK,強制轉換 1 為 int*,int* 自動轉換為 void*,再次隱式轉換為 Pointer
- // 注意:void* 到 int* 不能自動轉換,需要強制,參考 malloc() 的返回值
// baby pointer wrapper
class Pointer
{
public:
// 非 explicit 建構函式,說明 Pointer 可以從指標型別 void* 隱式轉換
Pointer(void* p) : m_Ptr(p)
{}
bool IsNull() const
{
return (m_Ptr == NULL);
}
private:
void* m_Ptr;
};
// 形參可以從指標型別 void* 隱式轉換
void TestPointer(Pointer ptr)
{
_tprintf(_T("ptr is %sNULL\n"), ptr.IsNull() ? _T("") : _T("NOT "));
}
// 使用者程式碼
TestPointer(0); // OK,0 是型別自動的,0 被自動轉換為 void*,再次隱式轉換為 Pointer
TestPointer(NULL); // OK,NULL 就是 0,同上
TestPointer(1); // Error,C++ 中 1 不同於 0,它是確定的 int 型別,
// 只能提升轉換到 float/double 型別,不能自動轉換為指標
TestPointer((int*)1); // OK,強制轉換 1 為 int*,int* 自動轉換為 void*,再次隱式轉換為 Pointer
// 注意:void* 到 int* 不能自動轉換,需要強制,參考 malloc() 的返回值
limits.h 整數型別常量^
在 limits.h 中定義,定義了各種 int 型別 (unsigned, char, short, long, __int64) 的最小、最大值,如 SCHAR_MAX (signed char MAX)、UCHAR_MAX (unsigned char MAX)、USHRT_MAX (unsigned short MAX) 等。編譯時,如果 int 字面量超出這些範圍,會編譯出錯
float.h 浮點型別常量^
在 float.h 中定義,定義各種浮點型別 (float, double, long double) 的極限值,如最小、最大值,最小浮點差量 (epsilon) 等
例子:浮點數極限值:判斷浮點數是否相等^
- // 對比一個 double 是否為 0
- inline
- bool double_equal0(double n)
- {
- return (n >= 0 ? n < DBL_MIN : n > -DBL_MIN);
- }
- // 對比兩個 double 是否相等
- inline
- bool double_equal(double l, double r)
- {
- return (l >= r ? l - r < DBL_EPSILON : r - l < DBL_EPSILON);
- }
- // 列印函式的結果
- #define TEST_BOOL_FUNC(func) _tprintf(_T("%s: %s\n"), _TSTRINGIZE(func), func ? _T("TRUE") : _T("FALSE"))
- // 使用者程式碼
- // 對比 double 是否為 0 時,double_equal0() 更精確
- // 對比兩個 double 是否相等時,最好用 double_equal()
- TEST_BOOL_FUNC(double_equal0(0)); // TRUE
- TEST_BOOL_FUNC(double_equal0(DBL_EPSILON)); // FALSE
- TEST_BOOL_FUNC(double_equal0(-DBL_EPSILON)); // FALSE
- TEST_BOOL_FUNC(double_equal0(DBL_MIN)); // FALSE
- TEST_BOOL_FUNC(double_equal0(-DBL_MIN)); // FALSE
- TEST_BOOL_FUNC(double_equal(0, 0)); // TRUE
- TEST_BOOL_FUNC(double_equal(DBL_EPSILON, 0)); // FALSE
- TEST_BOOL_FUNC(double_equal(DBL_MIN, 0)); // TRUE
- TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_EPSILON)); // FALSE
- TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_EPSILON)); // FALSE
- TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_MIN)); // TRUE
- TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_MIN)); // TRUE
// 對比一個 double 是否為 0
inline
bool double_equal0(double n)
{
return (n >= 0 ? n < DBL_MIN : n > -DBL_MIN);
}
// 對比兩個 double 是否相等
inline
bool double_equal(double l, double r)
{
return (l >= r ? l - r < DBL_EPSILON : r - l < DBL_EPSILON);
}
// 列印函式的結果
#define TEST_BOOL_FUNC(func) _tprintf(_T("%s: %s\n"), _TSTRINGIZE(func), func ? _T("TRUE") : _T("FALSE"))
// 使用者程式碼
// 對比 double 是否為 0 時,double_equal0() 更精確
// 對比兩個 double 是否相等時,最好用 double_equal()
TEST_BOOL_FUNC(double_equal0(0)); // TRUE
TEST_BOOL_FUNC(double_equal0(DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal0(-DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal0(DBL_MIN)); // FALSE
TEST_BOOL_FUNC(double_equal0(-DBL_MIN)); // FALSE
TEST_BOOL_FUNC(double_equal(0, 0)); // TRUE
TEST_BOOL_FUNC(double_equal(DBL_EPSILON, 0)); // FALSE
TEST_BOOL_FUNC(double_equal(DBL_MIN, 0)); // TRUE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_EPSILON)); // FALSE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_MIN)); // TRUE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_MIN)); // TRUE
math.h 數學常量^
數學計算常用的浮點數常量,如 M_PI (pi), M_E (e), M_SQRT2 (sqrt(2)) 等。這些數學常量不是標準 C/C++ 的一部分,而是 Microsoft 的擴充套件,使用前需要定義 _USE_MATH_DEFINES:
- #define _USE_MATH_DEFINES
- #include <math.h>
#define _USE_MATH_DEFINES
#include <math.h>
EOF 常量^
EOF (end-of-file) 常量,定義為 (-1),有寬字元版 WEOF ((wint_t)(0xFFFF)),EOF 和 WEOF 在 stdio.h 中定義,還有 _TCHAR 版 _TEOF,在 tchar.h 中定義。EOF 在流、I/O 操作中表示到達流、檔案末尾(EOF 條件),也用來表示發生錯誤情況
例子:標準輸入的 EOF^
- // 設定 locale
- // 定義寬字元流與控制檯 I/O 字元之間的轉換字符集編碼為系統 ANSI 字符集
- // 這樣在中文 Windows 上可輸入、顯示中文字元
- _tsetlocale(LC_ALL, _T(""));
- // 要用儲存空間 >= _gettchar() 返回值型別的變數儲存其返回值
- // 而不要用 char ch = _getchar(),那樣會截斷其返回值型別
- int ch;
- while ((ch = _gettchar()) != _TEOF)
- _tprintf(_T("[%c]"), (_TCHAR)ch);
- _tprintf(_T("\nread stdin: %s\n"), (feof(stdin) ? _T("EOF") : _T("Error")));
// 設定 locale
// 定義寬字元流與控制檯 I/O 字元之間的轉換字符集編碼為系統 ANSI 字符集
// 這樣在中文 Windows 上可輸入、顯示中文字元
_tsetlocale(LC_ALL, _T(""));
// 要用儲存空間 >= _gettchar() 返回值型別的變數儲存其返回值
// 而不要用 char ch = _getchar(),那樣會截斷其返回值型別
int ch;
while ((ch = _gettchar()) != _TEOF)
_tprintf(_T("[%c]"), (_TCHAR)ch);
_tprintf(_T("\nread stdin: %s\n"), (feof(stdin) ? _T("EOF") : _T("Error")));
測試輸出,用 Ctrl + Z 產生 EOF 訊號:
abc漢字
[a][b][/c][漢][字][
]^Z
read stdin: EOF
errno.h 錯誤程式碼^
在 errno.h 中定義,是測試全域性量 errno 的值,errno 在 VC 中實現為執行緒安全的函式,而非全域性變數。錯誤程式碼以 E 打頭如 EINVAL:不合法的引數錯誤
locale 類別^
locale 類別 (Categories),在 locale.h 中定義,如 LC_ALL、LC_CTYPE
_MAX_PATH 等檔名與路徑長度限制^
包括全路徑與各部分路徑的限制,即 FILENAME_MAX、_MAX_PATH、_MAX_DRIVE、_MAX_EXT、_MAX_FNAME、_MAX_DIR,在 stdlib.h 中定義。最大全路徑長度限制在 260,和 Windows 的 MAX_PATH 相同,這是為了相容 Windows 98 FAT32 檔案系統。CRT 支援 32767 長度的檔名,方法和 Windows API 相同,即使用 "\\?\" 路徑字首,並呼叫 Unicode 寬字元版的 CRT 函式
RAND_MAX 隨機數最大值^
在 stdlib.h 中定義為 32767,rand() 函式會產生 0 ~ RAND_MAX 之間的偽隨機 int 值
例子:用 RAND_MAX 產生某個範圍內的隨機數^
- template<bool seed, typename Type>
- inline
- Type get_rand(Type min, Type max)
- {
- _ASSERT(max >= min);
- if (seed) // Release 方式編譯時,這個判斷語句會被優化掉
- srand((unsigned int) time(NULL));
- return (Type) (((double) rand() / (double) RAND_MAX) * (max - min) + min);
- }
- template<typename Type>
- inline
- Type get_rand_seed(Type min, Type max)
- {
- return get_rand<true>(min, max);
- }
- template<typename Type>
- inline
- Type get_rand_noseed(Type min, Type max)
- {
- return get_rand<false>(min, max);
- }
- // 使用者程式碼
- #define RANGE_MIN 10
- #define RANGE_MAX 100
- int randnum;
- randnum = get_rand_seed(RANGE_MIN, RANGE_MAX);
- randnum = get_rand_noseed(RANGE_MIN, RANGE_MAX);
template<bool seed, typename Type>
inline
Type get_rand(Type min, Type max)
{
_ASSERT(max >= min);
if (seed) // Release 方式編譯時,這個判斷語句會被優化掉
srand((unsigned int) time(NULL));
return (Type) (((double) rand() / (double) RAND_MAX) * (max - min) + min);
}
template<typename Type>
inline
Type get_rand_seed(Type min, Type max)
{
return get_rand<true>(min, max);
}
template<typename Type>
inline
Type get_rand_noseed(Type min, Type max)
{
return get_rand<false>(min, max);
}
// 使用者程式碼
#define RANGE_MIN 10
#define RANGE_MAX 100
int randnum;
randnum = get_rand_seed(RANGE_MIN, RANGE_MAX);
randnum = get_rand_noseed(RANGE_MIN, RANGE_MAX);
va_arg/va_start/va_end 訪問變長函式引數^
用於訪問類似 printf(const char* format, ...) 等變長函式引數的輔助巨集,在 stdarg.h 中宣告,參考 MSDN: va_arg, va_end, va_start
巨集實現的 CRT 函式^
在 VC CRT 中有些函式以巨集和函式兩種方式實現,如 getchar(),並優先使用巨集版本,
強制使用函式版的方法:
(1). 呼叫時給函式名加括號,如 (getchar)()
(2). 呼叫前,取消巨集版本的定義,如 #undef getchar
Microsoft 預定義巨集^
VC C/C++ 和 Microsoft 預定義巨集參考 MSDN: Predefined Macros
這些巨集可以分類如下:
平臺與系統類^
_M_IX86: IA32/x86 平臺
_M_IA64: IA64/IPF (Itanium Processor Family) 64bit 平臺
_M_X64: x64/x86-64/AMD64 平臺
WIN32, _WIN32: Win32 和 Win64 程式開發都會定義
_WIN64: Win64 程式開發
_CONSOLE: 控制檯 Windows 程式開發,連結 Console 子系統:/SUBSYSTEM:CONSOLE
_WINDOWS: 非控制檯 Windows 程式開發,連結 Windows 子系統:/SUBSYSTEM:WINDOWS
版本號類^
通常定義為數字,配合 #if (XXX >= 1000) 使用,啟動、禁用特定部分的程式碼、特性
_MSC_VER: VC 編譯器 cl 版本號。VC 2003 編譯器版本號 13.10 (_MSC_VER = 1310),VC 2005 編譯器版本號 14.00 (_MSC_VER = 1400)。用 cl /? 檢視編譯器版本號
_MFC_VER: MFC 版本號
_ATL_VER: ATL 版本號
__CLR_VER: CLR 版本號
WINVER: 目標 Windows 版本號
_WIN32_WINNT: 目標 Windows NT 版本號
_WIN32_WINDOWS: 目標 Windows 9x 版本號
_WIN32_IE: 目標 IE 版本號
工程配置管理類^
_DEBUG, NDEBUG: Debug/Release 編譯方式
UNICODE, _UNICODE, _MBCS: ANSI/UNICODE/MBCS 字符集支援
_AFXDLL: 動態連結 MFC (DLL)
_ATL_STATIC_REGISTRY, _ATL_DLL: 靜態/動態連結 ATL
_DLL: 動態連結 CRT (DLL),對應 /MD、/MDd 編譯選項
_MT: CRT 多執行緒支援,目前 4 種 CRT 連結方式 /MD、/MDd、/MT、/MTd 都支援多執行緒(VC 2005 已沒有單執行緒版 CRT),加上建立 DLL 模組的 /LD、/LDd,都定義 _MT
_MANAGED: 以 /clr、/clr:pure、/clr:safe 託管方式編譯時,定義為 1
__cplusplus_cli: 以 /clr、/clr:pure、/clr:safe 方式編譯時定義,VC 2005 中定義為 200406L
上面 1、2、3 類巨集通常和條件編譯預處理指令 #if/#ifdef/#ifndef 配合使用
輔助類^
__VA_ARGS__: 在函式式巨集中,代表變長部分引數 (...),參考 MSDN: Variadic Macros
__COUNTER__: include 展開編譯單元后,編譯時第一次遇到 __COUNTER__ 替換為 0,以後在這個編譯每遇到一次 __COUNTER__ 自增一。不同的編譯單元之間 __COUNTER__ 不互相積累疊加,均從 0 開始計數,但預編譯頭 .pch 檔案會記錄 __COUNTER__ 的歷史值,則每個編譯單元均從歷史值 + 1 開始計數。__COUNTER__ 支援巨集的巢狀展開
__FUNCTION__, __FUNCDNAME__, __FUNCSIG__: 表示所在函式的函式名的 char 字串。例如,對於 void test_funcname_macro() 函式原型,它們的值如下:
(1). __FUNCTION__ = test_funcname_macro: 函式的原始名/非修飾名 (undecorated)
(2). __FUNCDNAME__ = [email protected]@YAXXZ: 函式的修飾名 (decorated),可用工具 undname "decorated_name" 得出函式原型和呼叫規範,即 __FUNCSIG__ 所表示的
(3). __FUNCSIG__ = void __cdecl test_funcname_macro(void): 函式的 signature 名,即呼叫約定、返回值型別、引數型別
例子:用 __VA_ARGS__ 列印跟蹤函式呼叫^
這個 CALL_TRACE 功能不實用,只為說明 __VA_ARGS__ 用法:
- // 針對引數不為 void,且需要儲存返回值的函式
- #define CALL_TRACE(func, ret, ...) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(__VA_ARGS__); }
- // 針對返回值為 void 或不關心返回值的函式
- #define CALL_TRACE_VOID(func, ...) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(__VA_ARGS__); }
- // 針對引數為 void 的函式
- // NOTE: 函式 func() 使用 func(__VA_ARGS__) 展開時,會影響前面的變長引數函式 _tprintf(),
- // 導致執行時緩衝區訪問違例(Debug 方式產生保護中斷),所以不能用前兩版帶 func(__VA_ARGS__) 的 CALL_TRACE
- #define CALL_TRACE_VOIDPARM(func, ret) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(); }
- // 針對返回值、引數均為 void 的函式
- #define CALL_TRACE_VOID_VOIDPARM(func) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(); }
- // 使用者程式碼
- // Unicode 方式編譯時,輸出 call: CreateFileW,並將返回值傳給 hFile
- CALL_TRACE_RET(CreateFile, hFile, _T("bbb"), 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// 針對引數不為 void,且需要儲存返回值的函式
#define CALL_TRACE(func, ret, ...) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(__VA_ARGS__); }
// 針對返回值為 void 或不關心返回值的函式
#define CALL_TRACE_VOID(func, ...) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(__VA_ARGS__); }
// 針對引數為 void 的函式
// NOTE: 函式 func() 使用 func(__VA_ARGS__) 展開時,會影響前面的變長引數函式 _tprintf(),
// 導致執行時緩衝區訪問違例(Debug 方式產生保護中斷),所以不能用前兩版帶 func(__VA_ARGS__) 的 CALL_TRACE
#define CALL_TRACE_VOIDPARM(func, ret) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(); }
// 針對返回值、引數均為 void 的函式
#define CALL_TRACE_VOID_VOIDPARM(func) { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(); }
// 使用者程式碼
// Unicode 方式編譯時,輸出 call: CreateFileW,並將返回值傳給 hFile
CALL_TRACE_RET(CreateFile, hFile, _T("bbb"), 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
例子:用 __VA_ARGS__ 格式化 std::string^
- namespace std
- {
- typedef std::basic_string<_TCHAR> _tstring;
- }
- #define FORMAT_STRING(str, buf, sz, ...) { sprintf_s(buf, sz, __VA_ARGS__); str = buf; }
- #define FORMAT_WSTRING(str, buf, sz, ...) { swprintf_s(buf, sz, __VA_ARGS__); str = buf; }
- #define FORMAT_TSTRING(str, buf, sz, ...) { _stprintf_s(buf, sz, __VA_ARGS__); str = buf; }
- // 使用者程式碼
- _TCHAR buf[512];
- _tstring str;
- FORMAT_TSTRING(str, buf, _countof(buf), _T("%s is: %f"), _T("Pi"), M_PI);
namespace std
{
typedef std::basic_string<_TCHAR> _tstring;
}
#define FORMAT_STRING(str, buf, sz, ...) { sprintf_s(buf, sz, __VA_ARGS__); str = buf; }
#define FORMAT_WSTRING(str, buf, sz, ...) { swprintf_s(buf, sz, __VA_ARGS__); str = buf; }
#define FORMAT_TSTRING(str, buf, sz, ...) { _stprintf_s(buf, sz, __VA_ARGS__); str = buf; }
// 使用者程式碼
_TCHAR buf[512];
_tstring str;
FORMAT_TSTRING(str, buf, _countof(buf), _T("%s is: %f"), _T("Pi"), M_PI);
例子:用 __COUNTER__ 計數值定義掩碼常量^
這種方法限制很多,並不實用,如 MyMask 之後再定義另一個掩碼列舉型時,會從 __COUNTER__ 的歷史值而非 0 開始:
- // 保證 MAKE_MASK 在所有其它使用 __COUNTER__ 程式碼之前,這樣才能
- // 保證第一次 MAKE_MASK 時,產生 2 << 0
- #define MAKE_MASK0(maskname) maskname = 1
- #define MAKE_MASK(maskname) maskname = (2 << __COUNTER__) // 說明 __COUNTER__ 是支援巢狀展開的
- // 使用者程式碼
- enum MyMask
- {
- MAKE_MASK0(MASK_0), // 2^0: 1
- MAKE_MASK(MASK_1), // 2^1: 2 << 0
- MAKE_MASK(MASK_2), // 2^2: 2 << 1
- MAKE_MASK(MASK_3), // 2^3: 2 << 2
- MAKE_MASK(MASK_4) // 2^4: 2 << 3
- // 最大 MASK = MASK_31 2^31: 2 << 30
- };
// 保證 MAKE_MASK 在所有其它使用 __COUNTER__ 程式碼之前,這樣才能
// 保證第一次 MAKE_MASK 時,產生 2 << 0
#define MAKE_MASK0(maskname) maskname = 1
#define MAKE_MASK(maskname) maskname = (2 << __COUNTER__) // 說明 __COUNTER__ 是支援巢狀展開的
// 使用者程式碼
enum MyMask
{
MAKE_MASK0(MASK_0), // 2^0: 1
MAKE_MASK(MASK_1), // 2^1: 2 << 0
MAKE_MASK(MASK_2), // 2^2: 2 << 1
MAKE_MASK(MASK_3), // 2^3: 2 << 2
MAKE_MASK(MASK_4) // 2^4: 2 << 3
// 最大 MASK = MASK_31 2^31: 2 << 30
};
例子:用 __FUNCTION__ 列印跟蹤函式呼叫^
- #define BEGIN_FUNC _tprintf(_T("%s BEGIN\n"), _T(__FUNCTION__));
- #define END_FUNC _tprintf(_T("%s END\n"), _T(__FUNCTION__));
- // 使用者程式碼
- void test_funcname_macro()
- {
- BEGIN_FUNC
- // 函式的功能程式碼
- END_FUNC
- }
#define BEGIN_FUNC _tprintf(_T("%s BEGIN\n"), _T(__FUNCTION__));
#define END_FUNC _tprintf(_T("%s END\n"), _T(__FUNCTION__));
// 使用者程式碼
void test_funcname_macro()
{
BEGIN_FUNC
// 函式的功能程式碼
END_FUNC
}
Windows API 中的註釋性巨集^
註釋性巨集,即是否使用它們不影響編譯結果,通常定義為空
目的:
(1). 在原始碼中起到註解 (annotation) 和標註 (marker) 作用,便於閱讀和理解程式碼功能
(2). 指導 lint 等靜態程式碼檢查工具檢查程式碼缺陷
(3). 指導文件自動生成工具掃描原始檔,生成類、函式/API 參考文件
如 WinDef.h 中定義的 IN、OUT、OPTIONAL 用來說明函式引數或型別成員的傳入、傳出、可選性質
sal.h 中有更完整和複雜的註釋性巨集,SAL (Source code Annotation Language) 參考 sal.h 原始檔和 MSDN: SAL Annotations
Windows API 和 CRT 都用 SAL 註釋,幾個常用的如下:
__in: 傳入引數
__out: 傳出引數
__inout: 傳入且傳出引數
__in_opt, __out_opt, __inout_opt: 可選引數,可以為 NULL
如 CreateFileW() 的宣告:
- // WinBase.h
- WINBASEAPI
- __out
- HANDLE
- WINAPI
- CreateFileW(
- __in LPCWSTR lpFileName,
- __in DWORD dwDesiredAccess,
- __in DWORD dwShareMode,
- __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
- __in DWORD dwCreationDisposition,
- __in DWORD dwFlagsAndAttributes,
- __in_opt HANDLE hTemplateFile
- );
// WinBase.h
WINBASEAPI
__out
HANDLE
WINAPI
CreateFileW(
__in LPCWSTR lpFileName,
__in DWORD dwDesiredAccess,
__in DWORD dwShareMode,
__in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
__in DWORD dwCreationDisposition,
__in DWORD dwFlagsAndAttributes,
__in_opt HANDLE hTemplateFile
);
Windows API 中的常用巨集^
Windows API 包含大量旗標、掩碼、狀態碼、錯誤碼等常量式巨集
函式式巨集最常用的有:
型別輔助類^
BYTE HIBYTE(WORD wValue)
BYTE LOBYTE(WORD wValue)
WORD HIWORD(DWORD dwValue)
WORD LOWORD(DWORD dwValue)
WORD MAKEWORD(BYTE bLow, BYTE bHigh)
LONG MAKELONG(WORD wLow, WORD wHigh)
LRESULT MAKELRESULT(WORD wLow, WORD wHigh)
LPARAM MAKELPARAM(WORD wLow, WORD wHigh)
WPARAM MAKEWPARAM(WORD wLow, WORD wHigh)
GDI 類^
DWORD MAKEROP4(DWORD fore, DWORD back): used in MaskBlt()
LONG DIBINDEX(WORD wColorTableIndex)
COLORREF PALETTEINDEX(WORD wPaletteIndex)
COLORREF PALETTERGB(BYTE bRed, BYTE bGreen, BYTE bBlue)
COLORREF RGB(BYTE byRed, BYTE byGreen, BYTE byBlue)
BYTE GetBValue(DWORD rgb)
BYTE GetGValue(DWORD rgb)
BYTE GetRValue(DWORD rgb)
POINTS MAKEPOINTS(DWORD dwValue)
另外,BITMAP_WIDTHBYTES(bits) 不在 Windows API 中,但比較常用於點陣圖:
- // 輸入:點陣圖影象中一行的邏輯位數 = 點陣圖畫素寬 x 每畫素位數
- // 輸出:點陣圖影象中一行佔用的位元組數,按 4 Bytes 對齊
- #define BITMAP_WIDTHBYTES(bits) (((bits) + 31) >> 5 << 2)
// 輸入:點陣圖影象中一行的邏輯位數 = 點陣圖畫素寬 x 每畫素位數
// 輸出:點陣圖影象中一行佔用的位元組數,按 4 Bytes 對齊
#define BITMAP_WIDTHBYTES(bits) (((bits) + 31) >> 5 << 2)
錯誤處理類^
標記沒有使用的引數、變數輔助巨集^
UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_LOCAL_VARIABLE(V)
讓沒有使用的引數、變數不產生編譯警告,並且關閉 lint 缺陷檢查報告
錯誤碼、狀態碼^
Windows 有三大錯誤碼、狀態碼空間:
(1). Win32 狀態碼:GetLastError() 所返回,DWORD 型別,WinError.h 中定義
(2). COM 狀態碼:COM 函式用,HRESULT 型別,WinError.h 中定義
(3). 核心狀態碼:核心函式和低階 API 用,NTSTATUS 型別,ntstatus.h 中定義
狀態碼有關的巨集:
MAKE_HRESULT(sev, fac, code): 將 severity、facility、code 合併為 HRESULT
HRESULT_CODE(hr): 取得 HRESULT 的 code 部分
HRESULT_FACILITY(hr): 取得 HRESULT 的 facility 部分
HRESULT_SEVERITY(hr): 取得 HRESULT 的 severity 位
HRESULT_FROM_NT(nt_stat): 從 NTSTATUS 變換到 HRESULT
HRESULT_FROM_WIN32(win_err): 從 Win32 狀態碼變換到 HRESULT
SUCCEEDED(hr): HRESULT 是否表示成功
FAILED(hr): HRESULT 是否表示失敗
IS_ERROR(hr): HRESULT 是否表示一個錯誤
Win32 狀態碼沒有類似 MAKE_HRESULT 的巨集,自定義 Win32 狀態碼時可以用 mc (Message Compiler) 工具處理 .mc 指令碼,自動生成含自定義 Win32 狀態碼的標頭檔案,同時生成用於 FormatMessage() 的狀態碼文字描述,參考 MSDN:Message Compiler
也可以自定義用於 Win32 狀態碼的 MAKE_WINERR():
- // copy from WinError.h
- //
- // Values are 32 bit values layed out as follows:
- //
- // 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
- // 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
- // +---+-+-+-----------------------+-------------------------------+
- // |Sev|C|R| Facility | Code |
- // +---+-+-+-----------------------+-------------------------------+
- //
- // where
- //
- // Sev - is the severity code
- //
- // 00 - Success
- // 01 - Informational
- // 10 - Warning
- // 11 - Error
- //
- // C - is the Customer code flag
- //
- // R - is a reserved bit
- //
- // Facility - is the facility code
- //
- // Code - is the facility's status code
- //
- // Win32 狀態碼的各部分起始位、位掩碼和位長度
- #define WINERR_SEVERITY_BIT_LOW 30
- #define WINERR_SEVERITY_MASK 0xC0000000
- #define WINERR_SEVERITY_BIT_LEN 2
- #define WINERR_SEVERITY_VALUE(val) (((val) << WINERR_SEVERITY_BIT_LOW) & WINERR_SEVERITY_MASK)
- #define WINERR_CUSTOM_DEFINE_BIT_LOW 29
- #define WINERR_CUSTOM_DEFINE_MASK 0x20000000
- #define WINERR_CUSTOM_DEFINE_BIT_LEN 1
- #define WINERR_CUSTOM_DEFINE_FLAG (1 << WINERR_CUSTOM_DEFINE_BIT_LOW)
- #define WINERR_FACILITY_BIT_LOW 16
- #define WINERR_FACILITY_MASK 0x0FFF0000
- #define WINERR_FACILITY_BIT_LEN 12
- #define WINERR_FACILITY_VALUE(val) (((val) << WINERR_FACILITY_BIT_LOW) & WINERR_FACILITY_MASK)
- #define WINERR_CODE_BIT_LOW 0
- #define WINERR_CODE_MASK 0x0000FFFF
- #define WINERR_CODE_BIT_LEN 16
- #define WINERR_CODE_VALUE(val) (val) & WINERR_CODE_MASK
- // Win32 狀態碼中的嚴重級別 severity
- #define WINERR_SEVERITY_SUCCESS 0
- #define WINERR_SEVERITY_INFORM 1
- #define WINERR_SEVERITY_WARNING 2
- #define WINERR_SEVERITY_ERROR 3
- #define WINERR_SEVERITY_NOT_CARE 3
- // 自定義 Win32 狀態碼的巨集
- #define MAKE_WINERR(sev, fac, code) \
- ((DWORD)(WINERR_SEVERITY_VALUE(sev) | WINERR_CUSTOM_DEFINE_FLAG | WINERR_FACILITY_VALUE(fac) | WINERR_CODE_VALUE(code)))
// copy from WinError.h
//
// Values are 32 bit values layed out as follows:
//
// 3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
// +---+-+-+-----------------------+-------------------------------+
// |Sev|C|R| Facility | Code |
// +---+-+-+-----------------------+-------------------------------+
//
// where
//
// Sev - is the severity code
//
// 00 - Success
// 01 - Informational
// 10 - Warning
// 11 - Error
//
// C - is the Customer code flag
//
// R - is a reserved bit
//
// Facility - is the facility code
//
// Code - is the facility's status code
//
// Win32 狀態碼的各部分起始位、位掩碼和位長度
#define WINERR_SEVERITY_BIT_LOW 30
#define WINERR_SEVERITY_MASK 0xC0000000
#define WINERR_SEVERITY_BIT_LEN 2
#define WINERR_SEVERITY_VALUE(val) (((val) << WINERR_SEVERITY_BIT_LOW) & WINERR_SEVERITY_MASK)
#define WINERR_CUSTOM_DEFINE_BIT_LOW 29
#define WINERR_CUSTOM_DEFINE_MASK 0x20000000
#define WINERR_CUSTOM_DEFINE_BIT_LEN 1
#define WINERR_CUSTOM_DEFINE_FLAG (1 << WINERR_CUSTOM_DEFINE_BIT_LOW)
#define WINERR_FACILITY