C/C++ 中宏與預處理使用方法大全 (VC)

VC 中的宏使用方法參考 MSDN: Macros (C/C++)php

C/C++ 預約義宏^

__LINE__: 當前源文件的行號,整數
__FILE__: 當前源文件名,char 字符串,使用 /FC 選項產生全路徑
__DATE__: 當前編譯日期,char 字符串,格式 Aug 28 2011
__TIME__: 當前編譯時間,char 字符串,格式 06:43:59
__STDC__: 整數 1,表示兼容 ANSI/ISO C 標準,配合 #if 使用
__TIMESTAMP__: 最後一次修改當前文件的時間戳,char 字符串,格式 Sun Aug 28 06:43:57 2011
__cplusplus: 以 C++ 方式而非 C 語言方式編譯時定義,VC 2005 中定義爲 199711L,配合 #ifdef 使用程序員

例子:C/C++ 預約義宏的取值^web

源碼打印正則表達式

  1. // MacroTest.h  編程

  2. void PrintSourceInfo()  api

  3. {  安全

  4.     const _TCHAR* pszstdc;  網絡

  5.     const _TCHAR* pszcpp;  多線程

  6.   

  7. #if __STDC__  併發

  8.     pszstdc = _T("YES");  

  9. #else  

  10.     pszstdc = _T("NO");  

  11. #endif  

  12.   

  13. #ifdef __cplusplus  

  14.     pszcpp = _T("YES");  

  15. #else  

  16.     pszcpp = _T("NO");  

  17. #endif  

  18.   

  19.     _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s, ANSI/ISO C: %s, C++: %s\n"),  

  20.              _T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__), pszstdc, pszcpp);  

  21. }  

  22.   

  23. // 宏化的 PrintSourceInfo()  

  24. #define PRINT_SOURCE_INFO() \  

  25.     _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s\n"), \  

  26.              _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()

源碼打印

  1. // assert.h  

  2.   

  3. _CRTIMP void __cdecl _wassert(__in_z const wchar_t * _Message, __in_z const wchar_t *_File, __in unsigned _Line);  

  4.   

  5. #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )  

  6.   

  7. // crtdbg.h  

  8.   

  9. #define _ASSERT_EXPR(expr, msg) \  

  10.         (void) ((!!(expr)) || \  

  11.                 (1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \  

  12.                 (_CrtDbgBreak(), 0))  

  13.   

  14. #ifndef _ASSERT  

  15. #define _ASSERT(expr)   _ASSERT_EXPR((expr), NULL)  

  16. #endif  

  17.   

  18. #ifndef _ASSERTE  

  19. #define _ASSERTE(expr)  _ASSERT_EXPR((expr), _CRT_WIDE(#expr))  

  20. #endif  

CRT 的調試輸出宏 _RPTn()/_RPTFn(),n: 0 ~ 5
_RPTWn()/_RPTFWn() 是寬字符版

源碼打印

  1. // crtdbg.h  

  2.   

  3. #define _RPT_BASE(args) \  

  4.         (void) ((1 != _CrtDbgReport args) || \  

  5.                 (_CrtDbgBreak(), 0))  

  6.   

  7. #define _RPTF0(rptno, msg) \  

  8.         _RPT_BASE((rptno, __FILE__, __LINE__, NULL, "%s", msg))  

MFC 的診斷與調試輸出:ASSERT/VERIFY, ASSERT_VALID, TRACE/TRACEn^

MFC 的診斷宏 ASSERT()/VERIFY()、ASSERT_VALID()

源碼打印

  1. // afx.h  

  2.   

  3. #define ASSERT(f)          DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0)))  

  4. #define ASSERT_VALID(pOb)  DEBUG_ONLY((::AfxAssertValidObject(pOb, THIS_FILE, __LINE__)))  

MFC 的調試輸出宏 TRACE()/TRACEn(),n: 0 ~ 3

源碼打印

  1. // atltrace.h  

  2.   

  3. #ifndef ATLTRACE  

  4. #define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)  

  5. #define ATLTRACE2 ATLTRACE  

  6. #endif  

  7.   

  8. // afx.h  

  9.   

  10. #include <atltrace.h>  

  11. #define TRACE ATLTRACE  

  12.   

  13. #define THIS_FILE          __FILE__  

  14. #define VERIFY(f)          ASSERT(f)  

  15. #define DEBUG_ONLY(f)      (f)  

  16.   

  17. #define TRACE0(sz)              TRACE(_T("%s"), _T(sz))  

  18. #define TRACE1(sz, p1)          TRACE(_T(sz), p1)  

  19. #define TRACE2(sz, p1, p2)      TRACE(_T(sz), p1, p2)  

  20. #define TRACE3(sz, p1, p2, p3)  TRACE(_T(sz), p1, p2, p3)  

MFC 的調試版 new^

源碼打印

  1. // afx.h  

  2.   

  3. void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);  

  4. #define DEBUG_NEW new(THIS_FILE, __LINE__)  

  5.   

  6. // 用戶代碼  

  7.   

  8. // 調試版 new  

  9. #ifdef _DEBUG  

  10. #define new DEBUG_NEW  

  11. #endif  

CRT 和 C 標準庫中的宏^

VC CRT 和 C 標準庫中的宏參考 MSDN: Global Constants

NULL 空指針^

NULL 在 stddef.h, stdio.h, stdlib.h 等多個頭文件中定義,是地址/指針類型的 0,以下:

源碼打印

  1. #ifdef __cplusplus  

  2. #define NULL    0  

  3. #else  

  4. #define NULL    ((void *)0)  

  5. #endif  

C++ 中的 0 是類型自動的,因此用 0 定義 NULL;而 C 中 0 是肯定的 int 類型,因此須要強制

C++ 中,當 NULL 的相關操做數,如:對比操做 ptr == NULL,或函數的形參是指針類型時,或者可以「從指針類型隱式轉換」時,0 被自動轉換爲指針類型

例子:NULL 隱式轉換和 0 是類型自動的^

源碼打印

  1. // baby pointer wrapper  

  2. class Pointer  

  3. {  

  4. public:  

  5.     // 非 explicit 構造函數,說明 Pointer 能夠從指針類型 void* 隱式轉換  

  6.     Pointer(void* p) : m_Ptr(p)  

  7.     {}  

  8.   

  9.     bool IsNull() const  

  10.     {  

  11.         return (m_Ptr == NULL);  

  12.     }  

  13.   

  14. private:  

  15.     void*    m_Ptr;  

  16. };  

  17.   

  18. // 形參能夠從指針類型 void* 隱式轉換  

  19. void TestPointer(Pointer ptr)  

  20. {  

  21.     _tprintf(_T("ptr is %sNULL\n"), ptr.IsNull() ? _T("") : _T("NOT "));  

  22. }  

  23.   

  24. // 用戶代碼  

  25. TestPointer(0);         // OK,0 是類型自動的,0 被自動轉換爲 void*,再次隱式轉換爲 Pointer  

  26. TestPointer(NULL);      // OK,NULL 就是 0,同上  

  27. TestPointer(1);         // Error,C++ 中 1 不一樣於 0,它是肯定的 int 類型,  

  28.                         // 只能提高轉換到 float/double 類型,不能自動轉換爲指針  

  29. TestPointer((int*)1);   // OK,強制轉換 1 爲 int*,int* 自動轉換爲 void*,再次隱式轉換爲 Pointer  

  30.                         // 注意: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 字面量超出這些範圍,會編譯出錯

參考 MSDN: Integer Limits

float.h 浮點類型常量^

在 float.h 中定義,定義各類浮點類型 (float, double, long double) 的極限值,如最小、最大值,最小浮點差量 (epsilon) 等

參考 MSDN: Floating Limits

例子:浮點數極限值:判斷浮點數是否相等^

源碼打印

  1. // 對比一個 double 是否爲 0  

  2. inline  

  3. bool double_equal0(double n)  

  4. {  

  5.     return (n >= 0 ? n < DBL_MIN : n > -DBL_MIN);  

  6. }  

  7.   

  8. // 對比兩個 double 是否相等  

  9. inline  

  10. bool double_equal(double l, double r)  

  11. {  

  12.     return (l >= r ? l - r < DBL_EPSILON : r - l < DBL_EPSILON);  

  13. }  

  14.   

  15. // 打印函數的結果  

  16. #define TEST_BOOL_FUNC(func) _tprintf(_T("%s: %s\n"), _TSTRINGIZE(func), func ? _T("TRUE") : _T("FALSE"))  

  17.   

  18. // 用戶代碼  

  19. // 對比 double 是否爲 0 時,double_equal0() 更精確  

  20. // 對比兩個 double 是否相等時,最好用 double_equal()  

  21.   

  22. TEST_BOOL_FUNC(double_equal0(0));                       // TRUE  

  23. TEST_BOOL_FUNC(double_equal0(DBL_EPSILON));             // FALSE  

  24. TEST_BOOL_FUNC(double_equal0(-DBL_EPSILON));            // FALSE  

  25. TEST_BOOL_FUNC(double_equal0(DBL_MIN));                 // FALSE  

  26. TEST_BOOL_FUNC(double_equal0(-DBL_MIN));                // FALSE  

  27.   

  28. TEST_BOOL_FUNC(double_equal(0, 0));                     // TRUE  

  29. TEST_BOOL_FUNC(double_equal(DBL_EPSILON, 0));           // FALSE  

  30. TEST_BOOL_FUNC(double_equal(DBL_MIN, 0));               // TRUE  

  31. TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_EPSILON));   // FALSE  

  32. TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_EPSILON));   // FALSE  

  33. TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_MIN));       // TRUE  

  34. 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:

源碼打印

  1. #define _USE_MATH_DEFINES  

  2. #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^

源碼打印

  1. // 設置 locale  

  2. // 定義寬字符流與控制檯 I/O 字符之間的轉換字符集編碼爲系統 ANSI 字符集  

  3. // 這樣在中文 Windows 上可輸入、顯示中文字符  

  4. _tsetlocale(LC_ALL, _T(""));  

  5.   

  6. // 要用存儲空間 >= _gettchar() 返回值類型的變量保存其返回值  

  7. // 而不要用 char ch = _getchar(),那樣會截斷其返回值類型  

  8. int ch;  

  9. while ((ch = _gettchar()) != _TEOF)  

  10.     _tprintf(_T("[%c]"), (_TCHAR)ch);  

  11.   

  12. _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:不合法的參數錯誤

錯誤代碼具體值參考 MSDN: errno Constants 和 errno, _doserrno, _sys_errlist, and _sys_nerr

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 產生某個範圍內的隨機數^

源碼打印

  1. template<bool seed, typename Type>  

  2. inline  

  3. Type get_rand(Type min, Type max)  

  4. {  

  5.     _ASSERT(max >= min);  

  6.   

  7.     if (seed)       // Release 方式編譯時,這個判斷語句會被優化掉  

  8.         srand((unsigned int) time(NULL));  

  9.   

  10.     return (Type) (((double) rand() / (double) RAND_MAX) * (max - min) + min);  

  11. }  

  12.   

  13. template<typename Type>  

  14. inline  

  15. Type get_rand_seed(Type min, Type max)  

  16. {  

  17.     return get_rand<true>(min, max);  

  18. }  

  19.   

  20. template<typename Type>  

  21. inline  

  22. Type get_rand_noseed(Type min, Type max)  

  23. {  

  24.     return get_rand<false>(min, max);  

  25. }  

  26.   

  27. // 用戶代碼  

  28. #define RANGE_MIN   10  

  29. #define RANGE_MAX   100  

  30.   

  31. int randnum;  

  32. randnum = get_rand_seed(RANGE_MIN, RANGE_MAX);  

  33. 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

兩種實現方式的比較見 MSDN: Recommendations for Choosing Between Functions and Macros

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 版本號

工程配置管理類^

_DEBUGNDEBUG: 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

上面 一、二、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__ = ?test_funcname_macro@@YAXXZ: 函數的修飾名 (decorated),可用工具 undname "decorated_name" 得出函數原型和調用規範,即 __FUNCSIG__ 所表示的
(3). __FUNCSIG__ = void __cdecl test_funcname_macro(void): 函數的 signature 名,即調用約定、返回值類型、參數類型

例子:用 __VA_ARGS__ 打印跟蹤函數調用^

這個 CALL_TRACE 功能不實用,只爲說明 __VA_ARGS__ 用法:

源碼打印

  1. // 針對參數不爲 void,且須要保存返回值的函數  

  2. #define CALL_TRACE(func, ret, ...)      { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(__VA_ARGS__); }  

  3. // 針對返回值爲 void 或不關心返回值的函數  

  4. #define CALL_TRACE_VOID(func, ...)      { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(__VA_ARGS__); }  

  5.   

  6. // 針對參數爲 void 的函數  

  7. // NOTE: 函數 func() 使用 func(__VA_ARGS__) 展開時,會影響前面的變長參數函數 _tprintf(),  

  8. // 致使運行時緩衝區訪問違例(Debug 方式產生保護中斷),因此不能用前兩版帶 func(__VA_ARGS__) 的 CALL_TRACE  

  9. #define CALL_TRACE_VOIDPARM(func, ret)  { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(); }  

  10.   

  11. // 針對返回值、參數均爲 void 的函數  

  12. #define CALL_TRACE_VOID_VOIDPARM(func)  { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(); }  

  13.   

  14. // 用戶代碼  

  15. // Unicode 方式編譯時,輸出 call: CreateFileW,並將返回值傳給 hFile  

  16. CALL_TRACE_RET(CreateFile, hFile, _T("bbb"), 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);  

例子:用 __VA_ARGS__ 格式化 std::string^

源碼打印

  1. namespace std  

  2. {  

  3. typedef std::basic_string<_TCHAR>   _tstring;  

  4. }  

  5.   

  6. #define FORMAT_STRING(str, buf, sz, ...)    { sprintf_s(buf, sz, __VA_ARGS__); str = buf; }  

  7. #define FORMAT_WSTRING(str, buf, sz, ...)   { swprintf_s(buf, sz, __VA_ARGS__); str = buf; }  

  8. #define FORMAT_TSTRING(str, buf, sz, ...)   { _stprintf_s(buf, sz, __VA_ARGS__); str = buf; }  

  9.   

  10. // 用戶代碼  

  11. _TCHAR buf[512];  

  12. _tstring str;  

  13. FORMAT_TSTRING(str, buf, _countof(buf), _T("%s is: %f"), _T("Pi"), M_PI);  

例子:用 __COUNTER__ 計數值定義掩碼常量^

這種方法限制不少,並不實用,如 MyMask 以後再定義另外一個掩碼列舉型時,會從 __COUNTER__ 的歷史值而非 0 開始:

源碼打印

  1. // 保證 MAKE_MASK 在全部其它使用 __COUNTER__ 代碼以前,這樣才能  

  2. // 保證第一次 MAKE_MASK 時,產生 2 << 0  

  3. #define MAKE_MASK0(maskname)    maskname = 1  

  4. #define MAKE_MASK(maskname)     maskname = (2 << __COUNTER__)   // 說明 __COUNTER__ 是支持嵌套展開的  

  5.   

  6. // 用戶代碼  

  7. enum MyMask  

  8. {  

  9.     MAKE_MASK0(MASK_0), //  2^0:    1  

  10.     MAKE_MASK(MASK_1),  //  2^1:    2 << 0  

  11.     MAKE_MASK(MASK_2),  //  2^2:    2 << 1  

  12.     MAKE_MASK(MASK_3),  //  2^3:    2 << 2  

  13.     MAKE_MASK(MASK_4)   //  2^4:    2 << 3  

  14.     // 最大 MASK = MASK_31  2^31:   2 << 30  

  15. };  

例子:用 __FUNCTION__ 打印跟蹤函數調用^

源碼打印

  1. #define BEGIN_FUNC  _tprintf(_T("%s BEGIN\n"), _T(__FUNCTION__));  

  2. #define END_FUNC    _tprintf(_T("%s END\n"), _T(__FUNCTION__));  

  3.   

  4. // 用戶代碼  

  5. void test_funcname_macro()  

  6. {  

  7.     BEGIN_FUNC  

  8.     // 函數的功能代碼  

  9.     END_FUNC  

  10. }  

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() 的聲明:

源碼打印

  1. // WinBase.h  

  2.   

  3. WINBASEAPI  

  4. __out  

  5. HANDLE  

  6. WINAPI  

  7. CreateFileW(  

  8.     __in     LPCWSTR lpFileName,  

  9.     __in     DWORD dwDesiredAccess,  

  10.     __in     DWORD dwShareMode,  

  11.     __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,  

  12.     __in     DWORD dwCreationDisposition,  

  13.     __in     DWORD dwFlagsAndAttributes,  

  14.     __in_opt HANDLE hTemplateFile  

  15.     );  

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 中,但比較經常使用於位圖:

源碼打印

  1. // 輸入:位圖圖像中一行的邏輯位數 = 位圖像素寬 x 每像素位數  

  2. // 輸出:位圖圖像中一行佔用的字節數,按 4 Bytes 對齊  

  3. #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():

源碼打印

  1. //  copy from WinError.h  

  2. //  

  3. //  Values are 32 bit values layed out as follows:  

  4. //  

  5. //   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1  

  6. //   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  

  7. //  +---+-+-+-----------------------+-------------------------------+  

  8. //  |Sev|C|R|     Facility          |               Code            |  

  9. //  +---+-+-+-----------------------+-------------------------------+  

  10. //  

  11. //  where  

  12. //  

  13. //      Sev - is the severity code  

  14. //  

  15. //          00 - Success  

  16. //          01 - Informational  

  17. //          10 - Warning  

  18. //          11 - Error  

  19. //  

  20. //      C - is the Customer code flag  

  21. //  

  22. //      R - is a reserved bit  

  23. //  

  24. //      Facility - is the facility code  

  25. //  

  26. //      Code - is the facility's status code  

  27. //  

  28.   

  29. // Win32 狀態碼的各部分起始位、位掩碼和位長度  

  30.   

  31. #define WINERR_SEVERITY_BIT_LOW         30  

  32. #define WINERR_SEVERITY_MASK            0xC0000000  

  33. #define WINERR_SEVERITY_BIT_LEN         2  

  34. #define WINERR_SEVERITY_VALUE(val)      (((val) << WINERR_SEVERITY_BIT_LOW) & WINERR_SEVERITY_MASK)  

  35.   

  36. #define WINERR_CUSTOM_DEFINE_BIT_LOW    29  

  37. #define WINERR_CUSTOM_DEFINE_MASK       0x20000000  

  38. #define WINERR_CUSTOM_DEFINE_BIT_LEN    1  

  39. #define WINERR_CUSTOM_DEFINE_FLAG       (1 << WINERR_CUSTOM_DEFINE_BIT_LOW)  

  40.   

  41. #define WINERR_FACILITY_BIT_LOW         16  

  42. #define WINERR_FACILITY_MASK            0x0FFF0000  

  43. #define WINERR_FACILITY_BIT_LEN         12  

  44. #define WINERR_FACILITY_VALUE(val)      (((val) << WINERR_FACILITY_BIT_LOW) & WINERR_FACILITY_MASK)  

  45.   

  46. #define WINERR_CODE_BIT_LOW             0  

  47. #define WINERR_CODE_MASK                0x0000FFFF  

  48. #define WINERR_CODE_BIT_LEN             16  

  49. #define WINERR_CODE_VALUE(val)          (val) & WINERR_CODE_MASK  

  50.   

  51. // Win32 狀態碼中的嚴重級別 severity  

  52.   

  53. #define WINERR_SEVERITY_SUCCESS         0  

  54. #define WINERR_SEVERITY_INFORM          1  

  55. #define WINERR_SEVERITY_WARNING         2  

  56. #define WINERR_SEVERITY_ERROR           3  

  57. #define WINERR_SEVERITY_NOT_CARE        3  

  58.   

  59. // 自定義 Win32 狀態碼的宏  

  60. #define MAKE_WINERR(sev, fac, code)     \  

  61.     ((DWORD)(WINERR_SEVERITY_VALUE(sev) | WINERR_CUSTOM_DEFINE_FLAG | WINERR_FACILITY_VALUE(fac) | WINERR_CODE_VALUE(code)))  

調用規範類^

調用規範/約定參考 MSDN: Calling Conventions

Windows API 使用的調用規範名稱宏,在 WinDef.h 中定義:

源碼打印

  1. #define CALLBACK    __stdcall  

  2. #define WINAPI      __stdcall  

  3. #define WINAPIV     __cdecl  

  4. #define APIENTRY    WINAPI  

  5. #define APIPRIVATE  __stdcall  

  6. #define PASCAL      __stdcall  

COM 經常使用的調用規範輔助宏:

EXTERN_C: C 連接約定

STDAPI: __stdcall,C 連接約定,返回 HRESULT
STDAPI_(type): __stdcall,C 連接約定,返回 type 類型

STDMETHOD(method): __stdcall,返回 HRESULT 的類成員虛函數
STDMETHOD_(type, method): __stdcall,返回 type 類型的類成員虛函數
STDMETHODIMP: __stdcall,返回 HRESULT,對應 STDMETHOD(method) 實現
STDMETHODIMP_(type): __stdcall,返回 type 類型,對應 STDMETHOD_(type, method) 實現

國際化類^

WORD    LANGIDFROMLCID(LCID lcid)
WORD    MAKELANGID(USHORT primaryLang, USHORT subLang)
DWORD   MAKELCID(WORD langID, WORD sortID)
DWORD   MAKESORTLCID(WORD langID, WORD sortID, WORD sortVersion)
WORD    PRIMARYLANGID(WORD lgid)
WORD    SORTIDFROMLCID(LCID lcid)
WORD    SORTVERSIONFROMLCID(LCID lcid)
WORD    SUBLANGID(WORD lgid)

資源類^

LPTSTR  MAKEINTRESOURCE(WORD wInt)
BOOL    IS_INTRESOURCE(WORD wInt)

網絡類^

LPARAM  MAKEIPADDRESS(BYTE b0, BYTE b1, BYTE b2, BYTE b3)
BYTE    FIRST_IPADDRESS(LPARAM lParam)
BYTE    SECOND_IPADDRESS(LPARAM lParam)
BYTE    THIRD_IPADDRESS(LPARAM lParam)
BYTE    FOURTH_IPADDRESS(LPARAM lParam)
LPARAM  MAKEIPRANGE(BYTE low, BYTE high)

字符串化操做符 #^

將代碼中某個名字轉換爲字符串字面量,即「加引號」,參考 MSDN: Stringizing Operator

用 # 操做構造字符串化宏 STRINGIZE^

源碼打印

  1. #define __STRINGIZE(x)  # x  

  2. #define _STRINGIZE(x)   __STRINGIZE(x)  

  3. #define _TSTRINGIZE(x)  _T(_STRINGIZE(x))  

說明:

(1). # x 產生的是 char 字符串,非 wchar_t 字符串,需配合 _T() 使用

(2). _MACRO() 再次調用 __MACRO() 是一種針對 # 和 ## 操做的經常使用編寫技巧。由於 #、## 操做比較特殊,當它處於宏體中時,不會進行嵌套展開,如 __TSTRINGIZE(NULL) 展開爲 "NULL" 而非 "0",要想嵌套展開,再定義一層 _STRINGIZE() 調用 __STRINGIZE() 便可,_TSTRINGIZE(NULL) 展開爲 "0"

CRT 中的 STRINGIZE 定義^

CRT 中有相似上面的 STRINGIZE(),以及寬字符化字面量宏 _CRT_WIDE() 的定義:

源碼打印

  1. // crtdefs.h  

  2. #ifndef _CRT_STRINGIZE  

  3. #define __CRT_STRINGIZE(_Value) #_Value  

  4. #define _CRT_STRINGIZE(_Value) __CRT_STRINGIZE(_Value)  

  5. #endif  

  6.   

  7. #ifndef _CRT_WIDE  

  8. #define __CRT_WIDE(_String) L ## _String  

  9. #define _CRT_WIDE(_String) __CRT_WIDE(_String)  

  10. #endif  

STRINGIZE 的展開規則^

1. 若是 _STRINGIZE() 的參數是宏,那麼宏表明的實際值也將被展開,即嵌套展開

例子:用 STRINGIZE 查看宏的展開結果^

查看某個宏在當前編譯配置 (Debug/Release, ANSI/Unicode) 下,實際表示的東西,如某個 _t 系列函數、Windows API 究竟表示哪一個函數,能夠利用 _STRINGIZE():

源碼打印

  1. // 將輸出實際的行號、數字,而非字符串 "__LINE__"、"MAX_PATH"  

  2. _tprintf(_T("Line: %s\n"), _TSTRINGIZE(__LINE__));  

  3. _tprintf(_T("MAX_PATH: %s\n"), _TSTRINGIZE(MAX_PATH));  

  4.   

  5. // 判斷宏的當前值、調用了哪一個版本的 _t 系列函數、Windows API  

  6. _tprintf(_T("_DEBUG: %s, _UNICODE: %s\n"), _TSTRINGIZE(_DEBUG), _TSTRINGIZE(_UNICODE));  

  7. _tprintf(_T("_tprintf: %s\n"), _TSTRINGIZE(_tprintf));  

  8. _tprintf(_T("CreateFile: %s\n"), _TSTRINGIZE(CreateFile));  

輸出結果:

Line: 24
MAX_PATH: 260
_DEBUG: 1, _UNICODE: 1
_tprintf: wprintf
CreateFile: CreateFileW

2. 若是 _STRINGIZE() 的參數單純的變量、函數、類型、const、enum 常量,那麼只是將 _STRINGIZE() 括號中的東西加引號而已,以下:

源碼打印

  1. // 非 const、其它內部類型 double、char,結果都同樣  

  2. const int val = 260;  

  3.   

  4. // 枚舉常量  

  5. enum MUSIC_STATE  

  6. {  

  7.     ST_STOP,  

  8.     ST_PLAY,  

  9.     ST_PAUSE,  

  10.     ST_BUTT  

  11. };  

  12.   

  13. // 自定義結構、類  

  14. ClassTest obj;  

  15.   

  16. // 函數  

  17. void func(int a);  

  18.   

  19. // 下面輸出 _TSTRINGIZE() 括號中名字加上引號獲得的字符串,而非實際變量值  

  20. _tprintf(_T("int: %s, val: %s\n"), _TSTRINGIZE(int), _TSTRINGIZE(val));  

  21. _tprintf(_T("MUSIC_STATE: %s, ST_STOP: %s\n"), _TSTRINGIZE(MUSIC_STATE), _TSTRINGIZE(ST_STOP));  

  22. _tprintf(_T("ClassTest: %s, obj: %s\n"), _TSTRINGIZE(ClassTest), _TSTRINGIZE(obj));  

  23. _tprintf(_T("func: %s\n"), _TSTRINGIZE(func));  

輸出結果:

int: int, val: val
MUSIC_STATE: MUSIC_STATE, ST_STOP: ST_STOP
ClassTest: ClassTest, obj: obj
func: func

拼接操做符 ##^

將代碼中兩個名字拼接到一塊兒,造成一個名字。## 操做「不加引號」,參考 MSDN: Token-Pasting Operator

源碼打印

  1. #define __CONCAT(x, y)  x ## y  

  2. #define _CONCAT(x, y)   __CONCAT(x, y)  

## 與 # 同樣對其操做數不進行嵌套展開,因此 __CONCAT(aaa, __CONCAT(bbb, ccc)) 的展開結果是 aaa__CONCAT(bbb, ccc),而 _CONCAT(aaa, _CONCAT(bbb, ccc)) 的展開結果是 aaabbbccc。## 的結果是名字拼接,而不是字符串字面量,即不是 "aaabbbccc"

一般用 ## 操做拼接構造類型、變量、函數的名字

例子:_T() 的定義^

源碼打印

  1. // tchar.h  

  2. #ifdef _UNICODE  

  3. #define __T(x)      L ## x  

  4. #else  

  5. #define __T(x)      x  

  6.   

  7. #define _T(x)       __T(x)  

例子:Windows API 通用句柄類型的定義^

源碼打印

  1. // winnt.h  

  2. typedef void *HANDLE;  

  3. #define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name  

  4.   

  5. // 所以多數 Windows 句柄是指向樁結構的指針,如 HWND:  

  6.   

  7. // windef.h  

  8. DECLARE_HANDLE  (HWND);  

  9.   

  10. // HWND 定義展開後是:  

  11. struct HWND__  

  12. {  

  13.     int unused;  

  14. };  

  15. typedef struct HWND__ *HWND;  

例子:用 ## 構造函數名^

源碼打印

  1. // 音樂播放狀態常量  

  2. enum MUSIC_STATE  

  3. {  

  4.     ST_STOP,  

  5.     ST_PLAY,  

  6.     ST_PAUSE,  

  7.     ST_BUTT  

  8. };  

  9.   

  10. // 音樂播放狀態結構  

  11. // 裏面有一個用於處理特定狀態的回調函數 stat_proc  

  12. typedef struct _MusicState  

  13. {  

  14.     MUSIC_STATE     stat;  

  15.     const _TCHAR*   stat_name;  

  16.     int             (*stat_proc)(void*);  

  17. } MusicState;  

  18.   

  19. // 處理特定音樂播放狀態的函數  

  20. // 函數名的統一形式 proc_ ## stat,stat 是狀態常量的名字  

  21. int proc_ST_STOP(void*);  

  22. int proc_ST_PLAY(void*);  

  23. int proc_ST_PAUSE(void*);  

  24.   

  25. // 初始化音樂播放狀態結構  

  26. #define INIT_MUSIC_STATE(stat)  {stat, _TSTRINGIZE(stat), proc_ ## stat}  

  27.   

  28. MusicState g_MusicState[ST_BUTT] =  

  29. {  

  30.     INIT_MUSIC_STATE(ST_STOP),  

  31.     INIT_MUSIC_STATE(ST_PLAY),  

  32.     INIT_MUSIC_STATE(ST_PAUSE)  

  33. };  

TCHAR 統一字符類型和處理^

_TCHAR、_T()、_t 系列函數等東西叫作 Generic-Text Mapping,即便用宏進行統一字符類型編寫,在不一樣的字符集編碼工程配置 ANSI/UNICODE/MBCS 下替換爲不一樣的實際函數或類型,參考 MSDN:Generic-Text Mappings,Using Generic-Text MappingsUsing TCHAR.H Data Types with _MBCS

工程的字符集配置的宏定義:

ANSI (SBCS, ASCII): _UNICODE 和 _MBCS 均未定義,使用 char 單字節字符集編碼
UNICODE: _UNICODE 定義,使用 wchar_t 寬字符集編碼,VC 默認 wchar_t 2 字節
MBCS: _MBCS 定義,使用 char 變長字符集編碼,一個字符佔一個或多個 char

_TCHAR, _TEXT()/_T(), _t 系列函數^

根據 _UNICODE、_MBCS 的定義,調用 ANSI/UNICODE/MBCS 不一樣字符集版本的 CRT 函數,或產生字面量,多在 tchar.h 中聲明。_t 字符操做函數參考 MSDN:String Manipulation (CRT)

TCHAR, LPTSTR/LPCTSTR, TEXT(), A/W 版本 Windows API^

根據 UNICODE 的定義,調用 ANSI/UNICODE 不一樣字符集版本的 Windows API,或產生字面量,多在 WinBase.h、Windows.h 中聲明

不成文約定:帶 _ 前綴的代碼,一般對應 CRT,而不帶 _ 前綴的東西,一般對應 Windows API。A/W 版本 API 都是接收字符串參數的函數,但並不是全部接收字符串的 API 都有 A/W 兩個版本,如 GetProcAddress() 只是 A 版本函數,由於 DLL 中的導出符號用 ASCII 英文足夠了

宏的缺點和替代方法^

宏是預編譯行爲,所作的是名字替換,它的缺點和替代方法以下:

宏難於調試^

編譯時,宏不會產生用於調試的名字符號。如 #define MAX_PATH 260,在調試時,沒法找到其符號名 MAX_PATH,而只是 260 數字

常量式宏能夠用 const 和 enum 代替,在調試中能夠查看 const、enum 的符號名,而且 const、enum 和宏的運行時開銷是相同的(有使用 const、enum 時纔會分配內存):

源碼打印

  1. const char* DEF_FILENAME = "default.txt";  

  2.   

  3. enum BUF_SIZE  

  4. {  

  5.     BUF_SIZE_SMALL      = 64,  

  6.     BUF_SIZE_MEDIUM     = 256,  

  7.     BUF_SIZE_LARGE      = 1024  

  8. };  

另外,在 VC 2005 中,進行 C/C++ 源碼級別調試時,函數式宏沒法像 inline 或普通函數同樣使用 Step into 進入宏定義體的代碼,宏調用被視爲一條語句,只能使用 Go To Definition (F12) 跳轉到宏定義處查看代碼,而不能調試

宏的使用缺陷^

(1). 宏以字面形式展開,有反作用,典型的有兩種:

(a). 宏參數不加括號展開時改變邏輯,如 #define RECT_AREA(x, y) (x * y)

解決方法:定義宏時給參數的使用加上括號,如 #define RECT_AREA(x, y) ((x) * (y))

(b). 宏體爲多行語句,若是放到判斷語句中,而且不加 {} 包起來,只有第一句在判斷語句下執行,其它在判斷語句外,以下例:

源碼打印

  1. #define SWAP(v1, v2, tmp)   \  

  2.     tmp = v1;               \  

  3.     v1 = v2;                \  

  4.     v2 = tmp;  

  5.   

  6. // 用戶代碼  

  7. if (condition)  

  8.     SWAP(a, b, t);  // 邏輯問題  

  9.   

  10. if (condition) {  

  11.     SWAP(a, b, t);  // OK  

  12. }  

解決方法:定義宏時用 {} 或 do {} while(0) 包起來,以下:

源碼打印

  1. #define SWAP(v1, v2, tmp)   \  

  2.     do {                    \  

  3.         tmp = v1;           \  

  4.         v1 = v2;            \  

  5.         v2 = tmp;           \  

  6.     } while (0)  

(2). 宏對參數沒有類型檢查,宏的返回也不具備類型

(3). 函數式宏,不是函數,不能將其宏名做爲函數指針,即不能進行函數回調;也不能進行遞歸調用

函數式宏大多能用 inline 函數 + 函數 template 的方式代替,並保持相同的運行時開銷。但由於 inline 函數是一種 盡力而爲 (Try My Best) 的編譯器指示(inline 函數不必定 inline 化,inline 化的程度也不一樣),實際的開銷根據 inline 函數調用複雜程度(是否有遞歸、做爲函數指針)、不一樣編譯器、不一樣的工程配置(Debug/Release、編譯選項、編譯優化級別),inline 化有所不一樣

參考 MSDN: Inline Functions versus Macros

宏形成全局名字空間污染^

宏是全局名字空間的,容易形成名字污染、干擾,可用 const、enum、inline 解決。以下:

源碼打印

  1. class TestClass1  

  2. {  

  3. private:  

  4.     int m_Val;  

  5.   

  6. // private 限制對宏 MACRO_DEF_VAL 不起做用  

  7. #define MACRO_DEF_VAL   128  

  8.   

  9. public:  

  10.     static const int CONST_DEF_VAL = 128;  

  11.     enum { ENUM_DEF_VAL = 128 };  

  12. };  

  13.   

  14. class TestClass2  

  15. {  

  16. private:  

  17.     int m_Val;  

  18.   

  19. // 產生 C4005 警告:MACRO_DEF_VAL 被重複定義  

  20. #define MACRO_DEF_VAL   256  

  21.   

  22. public:  

  23.     static const int CONST_DEF_VAL = 256;  

  24.     enum { ENUM_DEF_VAL = 256 };  

  25. };  

  26.   

  27. // 用戶代碼  

  28.   

  29. // 宏 MACRO_DEF_VAL 是全局的,不能寫爲 TestClass1::MACRO_DEF_VAL  

  30. _tprintf(_T("TestClass1: %d, %d, %d\n"), MACRO_DEF_VAL, TestClass1::CONST_DEF_VAL, TestClass1::ENUM_DEF_VAL);  

  31. _tprintf(_T("TestClass2: %d, %d, %d\n"), MACRO_DEF_VAL, TestClass2::CONST_DEF_VAL, TestClass2::ENUM_DEF_VAL);  

輸出結果:

後面定義的宏 MACRO_DEF_VAL 的值將前面的覆蓋了:

TestClass1: 256, 128, 128
TestClass2: 256, 256, 256

優先使用宏的狀況^

不是全部的宏都能用 const、enum、inline 函數代替:

(1). 對於一些不對應單個函數、變量、常量,而且編碼量大、結構重複的整塊代碼,宏是最合適的選擇,如 MFC 的 RTTI 支持和消息映射結構

(2). 如 例子:C/C++ 預約義宏的取值 「說明 (2)」中所示,須要嵌套展開 __FILE__、__LINE__、__FUNCTION__ 等預約義宏的狀況,必需用宏,而不能用 inline 函數

條件編譯^

#if/#else/#elif/#ifdef/#ifndef/#if defined/#if !defined

#ifdef XXX 等價於 #if defined (XXX)
#ifndef XXX 等價於 #if !defined (XXX)

參考 MSDN: The #if, #elif, #else, and #endif Directives

例子:註釋大量代碼^

源碼打印

  1. #if 0  

  2. XXXXXXXXX  

  3. #endif  

  4.   

  5. #if FALSE  

  6. XXXXXXXXX  

  7. #endif  

例子:MFC 中的調試版代碼示例^

見上文「MFC 的調試版 new」

AssertValid() 參考 MSDN: MFC ASSERT_VALID and CObject::AssertValid

源碼打印

  1. // FrameWindow、Doc、View 等類都可覆蓋如下用於調試的診斷函數  

  2. // 而後能夠用 ASSERT_VALID() 診斷其對象狀態是否有效  

  3.   

  4. #ifdef _DEBUG  

  5.     virtual void AssertValid() const;  

  6.     virtual void Dump(CDumpContext& dc) const;  

  7. #endif  

  8.   

  9. // Release 版本的 GetDocument() 是 inline 的  

  10. #ifndef _DEBUG  

  11. inline CMyDoc* CMyView::GetDocument() const  

  12. {  

  13.     return reinterpret_cast<CMyDoc*>(m_pDocument);  

  14. }  

  15. #endif  

例子:DLL 工程導出符號^

工程 DllProj 中導出符號(變量、函數、類)的方法:在工程中定義宏 DLLPROJ_EXPORTS (/D "DLLPROJ_EXPORTS"),並在使用工程中保證沒有定義 DLLPROJ_EXPORTS

DllProj 導出符號的聲明文件 DllProj.h 以下,在使用工程中 #include 該文件:

源碼打印

  1. // DllProj.h  

  2.   

  3. #ifndef _DLLPROJ_H_  

  4. #define _DLLPROJ_H_  

  5.   

  6. #ifdef DLLPROJ_EXPORTS  

  7. #define DLLPROJ_API __declspec(dllexport)  

  8. #else  

  9. #define DLLPROJ_API __declspec(dllimport)  

  10. #endif  

  11.   

  12. #ifdef __cplusplus  

  13. #define EXTERN_C        extern "C"  

  14. #define EXTERN_C_BEGIN  extern "C" {  

  15. #define EXTERN_C_END    }  

  16. #else   // __cplusplus defined  

  17. #define EXTERN_C        extern  

  18. #define EXTERN_C_BEGIN  

  19. #define EXTERN_C_END  

  20. #endif  // __cplusplus NOT defined  

  21.   

  22. // 導出類  

  23. class DLLPROJ_API TestClass  

  24. {  

  25. public:  

  26.     TestClass();  

  27. };  

  28.   

  29. // 導出全局變量,以 C 的連接方式(修飾名、調用約定)  

  30. EXTERN_C DLLPROJ_API int g_TestVal;  

  31.   

  32. // 導出函數  

  33. DLLPROJ_API int TestFunc();  

  34.   

  35. #endif  // _DLLPROJ_H_  

例子:用 #undef 解決 wxWidgets 自定義事件連接 BUG^

BUG 參考:wxEvent derived event,Custom Events

BUG 觸發條件:以 DLL 方式使用 wxWidgets Windows 版本,即定義了 WXUSINGDLL,使用 DECLARE_EVENT_TYPE() 定義自定事件

BUG 表現:以 __declspec(dllimport) 修飾事件標識,實際上事件標識應該是一個模塊內變量,而非導入變量,出現連接問題。MinGW GCC 4 報連接錯誤,VC 2005 報連接警告:warning C4273: inconsistent dll linkage。BUG 的具體緣由請跟蹤 wxWidgets 源碼 event.h 中的 DECLARE_EVENT_TYPE() 和 dlimpexp.h 中的 WXDLLIMPEXP_CORE 定義

BUG 典型工程:開源下載器 MultiGet svn version 3

BUG 解決方法

方法 1. 不使用舊的 DECLARE_EVENT_TYPE() 而使用 DECLARE_LOCAL_EVENT_TYPE() 定義自定事件
方法 2. 使用 DECLARE_EVENT_TYPE() 先後包含 undefine_WXDLLIMPEXP_CORE.h、redefine_WXDLLIMPEXP_CORE.h 頭文件,以便取消和重定義 WXDLLIMPEXP_CORE

方法 2 中的 undefine_WXDLLIMPEXP_CORE.h、redefine_WXDLLIMPEXP_CORE.h 以及使用方法以下:

源碼打印

  1. // undefine_WXDLLIMPEXP_CORE.h  

  2.   

  3. // 不要用 #pragma once 等包含一次技巧,由於一個源文件中可能有多個  

  4. // BEGIN_DECLARE_EVENT_TYPES 自定義事件塊,這時要屢次包含本文件  

  5.   

  6. #ifdef WXDLLIMPEXP_CORE  

  7. #   define REMOVE_WXDLLIMPEXP_CORE  

  8. #   undef WXDLLIMPEXP_CORE      // 先取消 WXDLLIMPEXP_CORE 定義  

  9. #   define WXDLLIMPEXP_CORE     // 再將其定義爲空  

  10. #endif  

源碼打印

  1. // redefine_WXDLLIMPEXP_CORE.h  

  2.   

  3. // 不要用 #pragma once 等包含一次技巧  

  4.   

  5. #ifdef REMOVE_WXDLLIMPEXP_CORE  

  6. #   undef WXDLLIMPEXP_CORE  

  7. // 如下塊拷貝自 wx-2.8.10 dlimpexp.h,用於恢復 WXDLLIMPEXP_CORE 的原有定義  

  8. // BEGIN  

  9. #   ifdef WXMAKINGDLL_CORE  

  10. #       define WXDLLIMPEXP_CORE WXEXPORT  

  11. #       define WXDLLIMPEXP_DATA_CORE(type) WXEXPORT type  

  12. #   elif defined(WXUSINGDLL)  

  13. #       define WXDLLIMPEXP_CORE WXIMPORT  

  14. #       define WXDLLIMPEXP_DATA_CORE(type) WXIMPORT type  

  15. #   else /* not making nor using DLL */  

  16. #       define WXDLLIMPEXP_CORE  

  17. #       define WXDLLIMPEXP_DATA_CORE(type) type  

  18. #   endif  

  19. // END  

  20. #   undef REMOVE_WXDLLIMPEXP_CORE  

  21. #endif  

源碼打印

  1. // 用戶代碼  

  2.   

  3. // 定義 WXDLLIMPEXP_CORE 爲空  

  4. #include <undefine_WXDLLIMPEXP_CORE.h>  

  5.   

  6. // 自定義事件  

  7. BEGIN_DECLARE_EVENT_TYPES()  

  8.     DECLARE_EVENT_TYPE(wxEVENT_TEST_TRIGGERED, wxID_ANY)  

  9.     // DECLARE_LOCAL_EVENT_TYPE(wxEVENT_TEST_TRIGGERED, wxID_ANY)   // 用這個不會有 BUG  

  10. END_DECLARE_EVENT_TYPES()  

  11.   

  12. // 重定義 WXDLLIMPEXP_CORE  

  13. #include <redefine_WXDLLIMPEXP_CORE.h>  

說明

(1). #undef 是 #define 的反操做,取消宏定義,而不是將宏定義爲空。取消後的宏名能夠再次定義,而不產生重定義問題
(2). 大量嵌套的 #if 條件編譯結構,可以使用這種預處理縮進方法

預編譯頭文件^

預編譯頭文件 PCH (Precompiled Header) 是對某個編譯單元的編譯結果 (.pch),一般這個編譯單元命名爲 [stdafx.cpp include => stdafx.h](VC 工程標準)或 [common.cpp include => common.h]。與常規的編譯結果 (.obj) 不一樣的是,若是 .pch 的編譯單元源碼在兩次工程編譯期間不改變,則從新編譯工程時,不會從新編譯 .pch

PCH 的特色使它的源碼如 stdafx.h,適合放入不多更改的代碼,如標準庫、運行時庫、系統 API、第三方庫的頭文件,以及工程全局的設置和名字符號,在從新編譯工程時,這些代碼便不會從新編譯,以加快編譯速度

使用 PCH 的編譯命令^

以 VC 工程標準的 [stdafx.cpp include => stdafx.h] 預編譯頭文件編譯單元爲例,產生、使用 PCH 的編譯命令選項以下:

  • 對除了 stdafx.cpp 以外的其它編譯單元 .c/.cpp(沒有 .h/.hpp,.h/.hpp 是經過 #include 展開到 .c/.cpp 造成編譯單元的),使用以下編譯選項(Debug 工程配置),若是使用 VC IDE 則在工程屬性頁中設置:

    /Yu"stdafx.h" /Fp"Debug\ProjName.pch"

    /Yu 表示經過 stdafx.h 使用 PCH,/Fp 指定使用的 PCH 路徑爲 Debug\ProjName.pch

  • 對 stdafx.cpp,若是使用 VC IDE 則在 stdafx.cpp 的屬性頁中設置,使用以下編譯選項:

    /Yc"stdafx.h" /Fp"Debug\ProjName.pch"

    /Yc 表示經過 stdafx.h 產生 PCH,/Fp 指定產生的 PCH 路徑爲 Debug\ProjName.pch

PCH 的詳細方法參考 MSDN: Creating Precompiled Header Files

例子:典型的 MFC 工程預編譯頭 stdafx.h 代碼^

源碼打印

  1. ////////////////////////////////////////////////////////////////////////////////  

  2. ///  

  3. /// @file       stdafx.h  

  4. /// @brief      Windows 標準預編譯頭文件  

  5. ///  

  6. /// 將標準庫、運行時庫、基本庫、Windows API、第三方庫的頭文件在這裏包含,生成  

  7. /// 預編譯頭文件 MFCBasic.pch  

  8. ///  

  9. /// 若是不修改 stdafx.h,增量編譯時便不會從新編譯 stdafx.h 中包含的頭文件,這樣  

  10. /// 加快了編譯速度  

  11. ///  

  12. /// @version    <version>  

  13. /// @author     <author>  

  14. /// @date       2011-07  

  15. ///  

  16. /// Copyright (c) 2011, <company>  

  17. /// All rights reserved.  

  18. ///  

  19. ////////////////////////////////////////////////////////////////////////////////  

  20.   

  21. // 典型的「只包含一次」條件編譯技巧  

  22. // VC cl 編譯器版本 10 以上 (_MSC_VER > 1000) 也可使用 #pragma once 指令  

  23. #ifndef _STDAFX_H_  

  24. #define _STDAFX_H_  

  25.   

  26. // 排除不多使用的 Windows 頭文件  

  27.   

  28. #define WIN32_LEAN_AND_MEAN     // 適用於 Windows API  

  29.   

  30. #ifndef VC_EXTRALEAN  

  31. #define VC_EXTRALEAN            // 適用於 MFC  

  32. #endif  

  33.   

  34. // 指定目標系統和環境 (Windows, IE) 的版本號  

  35.   

  36. #ifndef WINVER  

  37. #define WINVER          0x0501  // 目標系統具備 Windows XP 及以上特性  

  38. #endif  

  39.   

  40. #ifndef _WIN32_WINNT  

  41. #define _WIN32_WINNT    0x0501  // 目標系統具備 Windows XP 及以上特性  

  42. #endif  

  43.   

  44. #ifndef _WIN32_WINDOWS  

  45. #define _WIN32_WINDOWS  0x0410  // 目標系統具備 Windows 98 及以上特性  

  46. #endif  

  47.   

  48. #ifndef _WIN32_IE  

  49. #define _WIN32_IE       0x0600  // 目標系統具備 IE 6.0 及以上特性  

  50. #endif  

  51.   

  52. ////////////////////////////////////////////////////////////////////////////////  

  53. /// Include Header  

  54. ////////////////////////////////////////////////////////////////////////////////  

  55.   

  56. // C 標準庫與運行時庫 (CRT)  

  57. // BEGIN  

  58. //  

  59. #define _CRT_SECURE_NO_DEPRECATE    // 使用廢棄 (deprecated) 的 CRT 函數時,不產生編譯警告  

  60. #define _CRT_SECURE_NO_WARNINGS     // 典型的廢棄函數有不帶緩衝區大小檢查的 strcpy()、strcat()、sprintf() 等  

  61.   

  62. #include <stdlib.h>  

  63. #include <tchar.h>  

  64. #include <crtdbg.h>  

  65. #include <string.h>  

  66. //  

  67. // END  

  68.   

  69. // C++ 標準庫  

  70. // BEGIN  

  71. //  

  72. #include <exception>  

  73. #include <typeinfo>  

  74. //  

  75. // END  

  76.   

  77. // MFC 庫  

  78. // BEGIN  

  79. //  

  80. #ifndef _SECURE_ATL  

  81. #define _SECURE_ATL 1                       // ATL/MFC 的安全設置  

  82. #endif  

  83.   

  84. #define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS  // 使 ATL/MFC 的 CString 具備顯式地構造函數 (explicit)  

  85.   

  86. #define _AFX_ALL_WARNINGS                   // 打開 MFC 的全部警告,包括通常能夠安全忽略的警告  

  87.   

  88. #include <afxwin.h>     // MFC 核心和標準支持  

  89. #include <afxext.h>     // MFC 擴展支持  

  90.   

  91. #ifndef _AFX_NO_OLE_SUPPORT  

  92. #include <afxdtctl.h>   // MFC 的 IE 4 通用控件支持  

  93. #endif  

  94.   

  95. #ifndef _AFX_NO_AFXCMN_SUPPORT  

  96. #include <afxcmn.h>     // MFC 的 Windows 通用控件支持  

  97. #endif  

  98. //  

  99. // END  

  100.   

  101. // Windows API  

  102. // BEGIN  

  103. //  

  104. // #include <Windows.h>     // 使用 MFC 庫時不要包含 Windows.h,MFC 頭文件中已包含  

  105. #include <Winsock2.h>  

  106. //  

  107. // END  

  108.   

  109. // Windows 通用控件 ComCtl32.dll 版本 6.0 的內嵌 manifest  

  110. // BEGIN  

  111. //  

  112. #ifdef _UNICODE  

  113. #if defined _M_IX86  

  114. #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")  

  115. #elif defined _M_IA64  

  116. #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")  

  117. #elif defined _M_X64  

  118. #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")  

  119. #else  

  120. #pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")  

  121. #endif  

  122. #endif  // _UNICODE  

  123. //  

  124. // END  

  125.   

  126. #endif  // _STDAFX_H_  

經常使用預處理指令^

VC 支持的預處理指令參考 MSDN: Preprocessor Directives

#error 產生人工編譯錯誤^

#error 產生 fatal error,編譯器輸出 #error 後的提示文本。指示該源文件必需使用 C++ 方式編譯,以下:

源碼打印

  1. // test.cpp  

  2. #ifndef __cplusplus  

  3. #error MUST use C++ compilation  

  4. #endif  

以 C 語言方式編譯上面源文件 (/Tc test.cpp) 時報錯:

fatal error C1189: #error :  MUST use C++ compilation

#line 改變行號和源文件名^

#line 改變 __FILE__ 和 __LINE__ 的取值,例如:

源碼打印

  1. // 實際文件名:test_01.cpp  

  2. #line 1 "test_02.cpp"  

  3.     _tprintf(_T("File: %s, Line: %d\n"), _T(__FILE__), __LINE__);   // 實際第 200 行  

輸出:

File: test_02.cpp, Line: 1

# 空指令^

沒有做用的合法預處理指令行正則表達式:[\t ]*#[\t ]*

#pragma 預處理指令^

#pragma 是一組編譯器特定的預處理指令,每種編譯器的 #pragma 的子指令都有所不一樣。VC 的 #pragma 指令參考 MSDN: Pragma Directives and the __Pragma Keyword

經常使用的 #pragma 指令以下:

#pragma once 只包含一次頭文件^

對頭文件只包含一次,以下:

源碼打印

  1. // test.h  

  2. #if _MSC_VER > 1000  

  3. #pragma once  

  4. #endif  

  5.   

  6. // 頭文件中的代碼  

它和傳統的 #ifndef 只包含一次技巧的功能相同:

源碼打印

  1. // test.h  

  2. #ifndef _TEST_H_  

  3. #define _TEST_H_  

  4.   

  5. // 頭文件中的代碼  

  6.   

  7. #endif  // _TEST_H_  

在源文件中屢次 #include 包含 test.h 時,不會出現 redefinition 錯誤

#pragma message 編譯時輸出消息^

#pragma message 在編譯過程當中,向標準輸出或 VC 的 Output 窗口打印指定消息,做用:(1) 告知程序員代碼編譯和使用的注意事項 (2) 用於查看和診斷實際的編譯代碼

例子:用 #pragma message 和 STRINGIZE 查看宏的展開結果^

例 11 是用 STRINGIZE 在運行時輸出宏的展開結果,其實在編譯時也能夠用 #pragma message 輸出,診斷編譯的實際代碼:

源碼打印

  1. // 將輸出實際的行號、數字,而非字符串 "__LINE__"、"MAX_PATH"  

  2. #pragma message("Line: " _STRINGIZE(__LINE__))  

  3. #pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))  

  4.   

  5. // 判斷宏的當前值、調用了哪一個版本的 _t 系列函數、Windows API  

  6. #pragma message("_DEBUG: " _STRINGIZE(_DEBUG) ", _UNICODE: " _STRINGIZE(_UNICODE))  

  7. #pragma message("_tprintf: " _STRINGIZE(_tprintf))  

  8. #pragma message("CreateFile: " _STRINGIZE(CreateFile))  

在標準輸出或 VC 的 Output 窗口輸出:

Line: 209
MAX_PATH: 260
_DEBUG: 1, _UNICODE: 1
_tprintf: wprintf
CreateFile: CreateFileW

#pragma push_macro/pop_macro 保存和恢復宏定義^

#pragma push_macro/pop_macro 用來解決宏命名衝突問題,以下:

源碼打印

  1. // 保存來自 windef.h 的 MAX_PATH 定義  

  2. #pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))  

  3. #pragma push_macro("MAX_PATH")  

  4.   

  5. // 對 MAX_PATH 進行新的定義  

  6. // 即便以前沒有定義 MAX_PATH,#undef MAX_PATH 也不會報錯  

  7. #undef MAX_PATH  

  8. #define MAX_PATH    512  

  9. #pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))  

  10.   

  11. // 使用新的 MAX_PATH  

  12.   

  13. // 恢復 windef.h 的 MAX_PATH 定義  

  14. #pragma pop_macro("MAX_PATH")  

  15. #pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))  

#pragma message 輸出以下:

MAX_PATH: 260
MAX_PATH: 512
MAX_PATH: 260

#pragma warning 禁用和啓用編譯警告^

例子:

源碼打印

  1. // 禁用 C450七、C4034 警告(VC 的 warning 編號從 C4001 開始)  

  2. // 只報告一次 C4385 警告  

  3. // 將 C4164 警告做爲編譯錯誤  

  4. #pragma warning(disable: 4507 34; once: 4385; error: 164)  

  5.   

  6. #pragma warning(push)           // 保存當前的警告設置:全局警告級別和 disable 的  

  7. #pragma warning(disable: 4705)  // 禁用某些警告  

  8. #pragma warning(disable: 4706)  

  9. #pragma warning(disable: 4707)  

  10.   

  11. // 會產生 C470五、C470六、C4707 警告的代碼  

  12. #pragma warning(pop)            // 恢復保存的警告設置  

#pragma comment 目標文件註釋和編譯選項傳遞^

#pragma comment 的做用是在編譯或連接過程當中,向 COFF 二進制目標文件或 PE 可執行文件 (.obj/.lib/.exe/.dll) 中插入字符串註釋。目的:

(1). 將版本、版權等信息插入到 COFF/PE 文件中,以便發佈
(2). 插入的字符串會做爲後續編譯階段(如連接)的選項,以便支持如 auto-link 等在源碼中設置編譯、連接選項

例子:用 #pragma comment(lib) 實現庫的 auto-link^

以例 17 DllProj 工程爲基礎,DllProj 定義有 DLLPROJ_EXPORTS 宏,DllProj.h 以下:

源碼打印

  1. // DllProj.h  

  2.   

  3. #ifndef _DLLPROJ_H_  

  4. #define _DLLPROJ_H_  

  5.   

  6. #ifdef DLLPROJ_EXPORTS  

  7. #define DLLPROJ_API __declspec(dllexport)  

  8. #else  

  9. #pragma comment(lib, "DllProj.lib")     // 指示在導入符號時連接 DllProj.lib  

  10. #define DLLPROJ_API __declspec(dllimport)  

  11. #endif  

  12.   

  13. // 省略代碼  

  14.   

  15. #endif  // _DLLPROJ_H_  

在使用工程中 #include 該文件,並設置庫搜索路徑 /LIBPATH,沒必要指定連接導入庫 DllProj.lib

#pragma comment(linker) 傳遞連接選項^

#pragma comment(linker, "link_option")

link_option 只能爲如下連接選項:

/DEFAULTLIB
/EXPORT
/INCLUDE
/MANIFESTDEPENDENCY
/MERGE
/SECTION

#pragma comment(linker, "/SECTION") 設置區段屬性^

設置區段屬性的方法有:

(1). 使用模塊定義文件 .def 的 SECTIONS 定義,見 MSDN: SECTIONS (C/C++)

(2). 使用 /SECTION 連接選項,見 MSDN: /SECTION (Specify Section Attributes)

(3). 使用 #pragma section 指令建立區段

對於上面的方法 (2),可用 #pragma comment(linker) 指定連接選項

例子:#pragma comment(linker, "/SECTION") 設置可讀寫、共享區段

源碼打印

  1. #pragma comment(linker, "/SECTION:.mydata,RWS")  

  2. #pragma data_seg(".mydata")  

  3.   

  4. // .mydata 區段中的變量定義  

  5.   

  6. #pragma data_seg()  

#pragma 區段操做^

區段屬性和標準區段名參考 /SECTION 連接選項。查看區段偏移地址 (RVA)、起始地址和屬性,用 dumpbin 工具:

dumpbin /SECTION:secname xxx.exe|xxx.dll

自定義區段名不能和標準區段名衝突。自定義區段名長度限制爲 8 個字符,超過 8 個會截斷

#pragma section 在目標文件中建立區段^

#pragma section 在.obj 中建立區段,並設置屬性 read, write, execute, shared, nopage, nocache, discard, remove

#pragma section 新建的區段不包括任何內容,需向新建的區段放置數據或代碼:

(1). 對於數據區段(全局變量)

(a). __declspec(allocate("secname")) 修飾
(b). #pragma bss_seg/const_seg/data_seg 指令

(2). 對於代碼區段(函數)

(a). #pragma alloc_text 指令
(b). #pragma code_seg 指令

例子:#pragma section 建立可讀寫、共享區段

源碼打印

  1. #pragma section(".mydata", read, write, shared)     // 在 .obj 中新建可讀寫、共享區段 .mydata  

  2. // #pragma comment(linker, "/SECTION:.mydata,RWS")  // 做用與上相似:在連接時調整區段屬性  

  3. #pragma data_seg(".mydata")                         // 將如下初始化數據放置到 .mydata  

  4.   

  5. // .mydata 區段中的變量定義  

  6.   

  7. #pragma data_seg()                                  // 恢復默認的初始化數據區段 .data  

  8.   

  9. __declspec(allocate(".mydata"))                     // 用  __declspec(allocate) 放置數據到區段  

  10. int g_Var = 0;  

#pragma alloc_text 將 C 連接約定的函數放置到區段^

#pragma alloc_text 只能應用於 C 連接約定的函數,即對於 C++ 編譯方式,需用 extern "C" 聲明函數。因此 #pragma alloc_text 不支持重載函數和類成員函數

#pragma alloc_text 在函數聲明與函數定義體之間使用

例子:在可執行、非分頁區段中,用 #pragma alloc_text 放置 C 連接約定函數

源碼打印

  1. extern "C" void TestFunc_1();                           // 必需在 .mycode 以前有函數聲明  

  2. extern "C" void TestFunc_2();                           // 而且是 C 連接約定的  

  3.   

  4. #pragma section(".mycode", read, execute, nopage)       // 創建可執行、非分頁區段  

  5. // #pragma comment(linker, "/SECTION:.mycode,RE!P")     // 做用與上相似:在連接時調整區段屬性  

  6. #pragma alloc_text(".mycode", TestFunc_1, TestFunc_2)   // 將指定函數放到 .mycode 中  

  7.   

  8. void TestFunc_1()  

  9. {  

  10.     // TestFunc_1 函數體  

  11. }  

  12.   

  13. void TestFunc_2()  

  14. {  

  15.     // TestFunc_2 函數體  

  16. }  

#pragma code_seg 將函數放置到代碼區段^

#pragma code_seg/bss_seg/const_seg/data_seg 會新建區段,以前沒必要有 #pragma section;若是以前有 #pragma section,會使用 #pragma section 設置的區段屬性

若是省略參數,會放置到標準區段:

#pragma code_seg(): .text
#pragma data_seg(): .data
#pragma bss_seg(): .bss
#pragma const_seg(): .rdata

例子:在可執行、非分頁區段中,用 #pragma code_seg 放置函數

源碼打印

  1. #pragma section(".mycode", read, execute, nopage)       // 創建可執行、非分頁區段  

  2. // #pragma comment(linker, "/SECTION:.mycode,RE!P")     // 做用與上相似:在連接時調整區段屬性  

  3.   

  4. #pragma code_seg(".mycode")                             // 將如下函數放到 .mycode 區段中  

  5. void TestFunc_1()  

  6. {  

  7.     // TestFunc_1 函數體  

  8. }  

  9.   

  10. void TestFunc_2()  

  11. {  

  12.     // TestFunc_2 函數體  

  13. }  

  14. #pragma code_seg()                                      // 恢復默認的標準代碼區段 .text  

#pragma data_seg/bss_seg/const_seg 將數據放置到區段^

數據性質和區段有對應關係,若是放置區段和數據性質衝突,則不會實際放到該區段中:

(1). .data 標準區段,放置初始化非 0 全局數據。用 #pragma data_seg 放置初始化數據,必需顯示初始化其中變量(能夠初始化爲 0),不然不會放入 #pragma data_seg 指定的區段

(2). .bss 標準區段,放置未初始化、默認或顯式初始化爲 0 的全局數據。注意連接時 .bss 會向 .data 中合併,因此在 .exe/.dll 中看不到 .bss 區段,可查看 .obj 中的 .bss 區段。用 #pragma bss_seg 放置未初始化數據,必需不初始化其中變量(也不能初始化爲 0),不然不會放入 #pragma bss_seg 指定的區段

(3). .rdata 標準區段,放置只讀的全局常量數據。const 數字類型會編碼到代碼中(指令當即數),因此不放到 .rdata 中。用 #pragma const_seg 放置只讀常量數據

例子:自定義區段和數據性質衝突

如下錯誤編譯器不會報錯,但實際沒有放置到指望的區段中

源碼打印

  1. int g_Var1 = 1;     // 放置到 .data 中  

  2.   

  3. int g_Var2 = 0;     // 放置到 .obj 的 .bss 中,連接時合併到 .data  

  4. int g_Var3;         // 同上  

  5.   

  6. const int g_Var4 = 1;           // 編碼到代碼中,沒有放置到 .rdata 中  

  7. const char g_szVar5[]= "foo";   // 放置到 .rdata 中  

  8.   

  9. #pragma const_seg(".myrdata")  

  10. const char g_szVar6[]= "foo";   // 放置到 .myrdata 中  

  11. #pragma const_seg()  

  12.   

  13. #pragma bss_seg(".mybss")  

  14. int g_Var7 = 1;     // 錯誤:放置到 .data 中  

  15. int g_Var8 = 0;     // 錯誤:放置到 .obj 的 .bss 中,連接時合併到 .data  

  16. int g_Var9;         // 正確放置到 .mybss 中  

  17. #pragma bss_seg()  

  18.   

  19. #pragma data_seg(".mydata")  

  20. int g_Var10 = 0;    // 正確放置到 .mydata 中  

  21. int g_Var11;        // 錯誤:放置到 .obj 的 .bss 中,連接時合併到 .data  

  22. #pragma data_seg()  

#pragma pack 設置成員字節對齊^

設置 struct, union, class 成員字節對齊的方法:

(1). 編譯選項 /Zp,x86 缺省爲 /Zp8,即以 8 byte 對齊,參考 MSDN: /Zp (Struct Member Alignment)

(2). 使用 __declspec(align(#)) 修飾,參考 MSDN: align (C++)

(3). 使用 #pragma pack

#pragma pack(): 不帶參數的 #pragma pack() 表示恢復到編譯選項 /Zp 設置的字節對齊

#pragma pack(show): 產生一條 C4810 編譯警告,報告當前的字節對齊:

Test.cpp(140) : warning C4810: value of pragma pack(show) == 8

例子:使用 #pragma pack 設置成員字節對齊

源碼打印

  1. // x86 32bit  

  2.   

  3. #pragma pack(4)             // 用一對 #pragma pack(4) | #pragma pack()  

  4. // #pragma pack(push, 4)    // 用一對 #pragma pack(push, 4) | #pragma pack(pop)  

  5. struct TestStruct  

  6. {  

  7.     double  a;              // sizeof(double) = 8  

  8.     int     b;              // sizeof(int) = 4  

  9. };                          // sizeof(TestStruct) = 12  

  10.   

  11. // #pragma pack(4) 會一直做用,直到改變 pack   

  12.   

  13. #pragma pack()              // 恢復編譯選項 /Zp 設置的字節對齊  

  14. // #pragma pack(pop)        // 恢復 #pragma pack(push, 4) 以前的字節對齊  

#pragma inline 函數設置^

inline 函數修飾,參考 MSDN: inline, __inline, __forceinline

inline 函數編譯優化選項,參考 MSDN: /Ob (Inline Function Expansion)

#pragma auto_inline 禁用和啓用 auto-inline^

例子:

使用 /O2 編譯優化選項,含 /Ob2:啓動 auto-inline

源碼打印

  1. #pragma auto_inline(off)  

  2. int simple_func(int a)      // 不會 inline 化  

  3.   

  4. inline                      // #pragma auto_inline(off) 不會做用於顯式指定的 inline 函數  

  5. int simple_func2(int a)     // 會 inline 化  

#pragma inline_depth 設置函數調用的 inline 化深度^

#pragma inline_depth(n) 做用於 inline、__inline 和 /Ob2 選項下的 auto-inline 化函數,不做用於 __forceinline 函數。須要 /Ob1 或 /Ob2 編譯選項

n: 0 ~ 255,255 表示無限制調用深度 inline 化,0 表示禁止 inline 化,省略參數 #pragma inline_depth() 時 n = 254

遞歸函數 inline 化的最大調用深度爲 16 次調用

#pragma inline_recursion 禁用和啓用遞歸函數的 inline 化^

做用於 inline、__inline 和 /Ob2 選項下的 auto-inline 化函數。須要 /Ob1 或 /Ob2 編譯選項

默認爲 #pragma inline_recursion(off),這時一個可 inline 化的遞歸調用函數只 inline 展開一次。若是 #pragma inline_recursion(on),則 inline 展開深度由 #pragma inline_depth 限制,並不超過 16 次

#pragma 優化指令^

編譯優化選項 /O,參考 MSDN: /O Options (Optimize Code)

#pragma optimize 禁用或啓動特定優化^

#pragma optimize("gp(s|t)y", on|off)

優化參數和編譯優化選項之間的對應關係:

g: /Og
p: /fp:precise 浮點數一致性
s: /Os 生成最小代碼
t: /Ot 生成最快代碼
y: /Oy

源碼打印

  1. #pragma optimize("pt", on)  // 對下面的代碼使用 fp:precise, /Ot 優化  

  2.   

  3. // 函數定義  

  4.   

  5. #pragma optimize("", off)   // 關閉上次 #pragma optimize 指定的優化  

  6. #pragma optimize("", on)    // 恢復到編譯器 /O 選項指定的優化  

#pragma intrinsic 使用 intrinsic 函數^

使用 intrinsic 函數編譯選項 /Oi,參考 MSDN: /Oi (Generate Intrinsic Functions)

#pragma intrinsic,參考 MSDN: intrinsic

源碼打印

  1. #include <string.h>  

  2.   

  3. // 使用 /Oi 編譯選項  

  4.   

  5. #pragma intrinsic(memcpy)  

#pragma function 使用普通函數^

和 #pragma intrinsic 對應,改變 /Oi 選項或以前的 #pragma intrinsic 設置,使用指定函數名的普通函數版本

#pragma deprecated 聲明廢棄函數^

#pragma deprecated 用來聲明廢棄的函數、類型、宏,編譯器產生 C4995 警告

__declspec(deprecated) 修飾也可用來聲明廢棄的函數、類型,編譯器產生 C4996 警告

例子:

源碼打印

  1. // 使用 #pragma deprecated  

  2. // BEGIN  

  3. //  

  4. #pragma deprecated(OldClass)  

  5. class OldClass1;  

  6.   

  7. #pragma deprecated(test_func1)  

  8. void old_func1();  

  9. //  

  10. // END  

  11.   

  12. // 使用 __declspec(deprecated)  

  13. // BEGIN  

  14. #define DEPRECATED_WILL_RMOVED  "** will be removed in next version **"  

  15.   

  16. // deprecated() 中的字符串不是必需的,若是有,會在警告時輸出  

  17. __declspec(deprecated(DEPRECATED_WILL_RMOVED)) void old_func2();  

  18.   

  19. // 注意 __declspec(deprecated) 修飾 class 時的位置  

  20. class __declspec(deprecated) OldClass2;  

  21. //  

  22. // END  

  23.   

  24. void test()  

  25. {  

  26.     old_func1();    // 產生 C4995 警告  

  27.     OldClass1 obj;  // 產生 C4995 警告  

  28.   

  29.     old_func2();    // 產生 C4996 警告,並輸出 "** will be removed in next version **"  

  30.     OldClass2();    // 產生 C4996 警告  

  31. }  

#pragma omp 使用 OpenMP 指令^

指令形式:

#pragma omp omp_directive

用於多線程、併發編程的 OpenMP 指令,子指令 omp_directive 參考 MSDN: OpenMP Directives

#pragma region/endregion 摺疊代碼塊^

標記一整塊代碼,在 VC 編輯器可摺疊成一行 (+) 和展開,見 VC 的 Edit->Outlining 菜單

VC Outlining 經常使用快捷鍵:

Ctrl + M, Ctrl + L: 摺疊或展開全部的代碼塊
Ctrl + M, Ctrl + M: 摺疊或展開光標所在的代碼塊

源碼打印

  1. #pragma region FuncTestCode             // 摺疊成一行後,(+) 後顯示的名字  

  2.   

  3. // 這裏是一整塊代碼  

  4.   

  5. #pragma endregion Test Code of Func()   // 摺疊後在名字後顯示的註釋  

#pragma setlocale 設置源代碼中字符串字面量的編碼^

#pragma setlocale("locale")

#pragma setlocale() 使用的 locale 參數和 CRT 函數 setlocale() 的相同,參考 MSDN: Language Strings,如 簡體中文 "chs" (GBK),繁體中文 "cht" (BIG5),日文 "jpn" (JIS)。注意:GBK 包括簡體中文、繁體中文、日文,因此繁體中文的源文件不必定是 BIG5,也多是 GBK,要看實際的編碼

例子:

默認源代碼的設置爲 #pragma setlocale(""),"" 表示 Windows 用戶默認 ANSI 代碼頁,在控制面板中區域和語言選項中設置,默認簡體中文系統爲 GBK,繁體中文系統爲 BIG5 等。因此在簡體系統下編寫簡體字面量代碼,或在繁體系統下編寫繁體字面量代碼等,無需設置源文件的 #pragma setlocale

在簡體中文 Windows 下源文件使用 BIG5 編碼源文件,代碼中有 L"xxx" 的寬字符字面量,且 "xxx" 在 BIG5 - ASCII 的字符集範圍,則應當使用 #pragma setlocale("cht")

#pragma include_alias 定義頭文件別名^

源碼打印

  1. #pragma include_alias(<stdio.h>, <newstdio.h>)  

  2. #pragma include_alias("api.h", "test\api.h")  

  3.   

  4. #include <stdio.h>  

  5. #include "api.h"  

預處理相關編譯選項^

/D 定義宏^

/D: 定義宏,做用相似 #define,但會去掉選項中的引號
/U: 取消指定的預約義宏,相似 #undef,如 /U _DEBUG
/u: 取消全部的預約義宏。/U 和 /u 都不能取消在源碼中用 #define 定義的宏

定義數字^

  • /DTESTMACRO: 等價 #define TESTMACRO 1,整數

  • /DTESTMACRO=1: 同上

  • /DTESTMACRO="1": 同上

  • /DTESTMACRO=3.14: 等價 #define TESTMACRO 3.14,浮點數

  • /DTESTMACRO="3.14": 同上

定義字符串^

  • /DTESTMACRO="abcdef": 等價 #define TESTMACRO abcdef,非字符串字面量(沒有引號)

  • /DTESTMACRO=\"abcdef\": 等價 #define TESTMACRO "abcdef",字符串字面量

  • /DTESTMACRO="\"abcdef\"": 同上

空定義^

  • /DTESTMACRO=: 等價 #define TESTMACRO

  • /DTESTMACRO="": 同上

  • /DTESTMACRO=\"\": 等價 #define TESTMACRO "",非空定義,而是空字符串

CL 環境變量使用 /D^

SET CL=/DTESTMACRO#1: 用 # 代替 =,等價 /DTESTMACRO=1,即 #define TESTMACRO 1

/E, /EP, /P 預處理選項^

/E: 預處理源文件,結果輸出到標準輸出,去掉註釋,在 #inlcude 展開和條件編譯周圍產生 #line 行號指示
/EP: 和 /E 類似,結果輸出到標準輸出,但不產生 #line
/P: 和 /E 類似,產生 #line,結果輸出到文件 (test.cpp => test.i),至關於 cl /E test.cpp > test.i
/P /EP 聯用: 結果輸出到文件 test.i,且不產生 #line。/E、/EP、/P 不能和預編譯頭 PCH 聯用
/C: 預處理時保留註釋,和 /E、/P、/EP 聯用

例子:預處理展開源文件^

源文件 test.cpp:

源碼打印

  1. #include <stdio.h>  

  2.   

  3. int main()  

  4. {  

  5. #ifdef _DEBUG  

  6.     printf("Debug config\n");  

  7. #else  

  8.     printf("Release config\n");  

  9. #endif  

  10.   

  11. // MARK: TESTMACRO value  

  12.     printf("TESTMACRO: %d\n", TESTMACRO);  

  13.     return 0;  

  14. }  

預處理編譯命令:

cl /P /C /DTESTMACRO test.cpp

預處理輸出到 test.i:

源碼打印

  1. #line 1 "test.cpp"  

  2. #line 1 "d:\\Visual Studio 8\\VC\\INCLUDE\\stdio.h"  

  3.   

  4. // #line 中 stdio.h 的路徑由實際 VC 安裝路徑而定  

  5. // 這裏省略 stdio.h 展開後的大量代碼  

  6.   

  7. #line 706 "d:\\Visual Studio 8\\VC\\INCLUDE\\stdio.h"  

  8.   

  9. #line 2 "test.cpp"  

  10.   

  11. int main()  

  12. {  

  13.   

  14.   

  15.   

  16.     printf("Release config\n");  

  17. #line 10 "test.cpp"  

  18.   

  19. // MARK: TESTMACRO value  

  20.     printf("TESTMACRO: %d\n", 1);  

  21.     return 0;  

  22. }  

例子:過濾查看預處理展開結果^

用這種方法能夠查看編譯過程當中,實際的宏展開、預處理結果

以上面的 test.cpp 爲例,預處理編譯命令和 grep 過濾:

cl /EP /C /DTESTMACRO test.cpp 2>NUL | egrep -A 5 -B 5 "MARK: TESTMACRO"

2>NUL: 用於屏蔽輸出 VC 編譯器 banner 和提示、錯誤信息,用 /nologo 選項也能夠
egrep -A 5 -B 5: 表示輸出匹配正則表達式先後 5 行

輸出結果以下:

    printf("Release config\n");

// MARK: TESTMACRO value
    printf("TESTMACRO: %d\n", 1);
    return 0;
}

/showIncludes 輸出頭文件列表^

輸出源文件的 #include 的頭文件列表到 stderr,包括嵌套 #include

例子:查看 #include 頭文件列表^

以上面 test.cpp 爲例,編譯命令:

cl /nologo /showIncludes /EP test.cpp >NUL

輸出頭文件列表,由實際 VC 安裝路徑而定。嵌套 #include 用空格縮進表示,如 stdio.h include=> crtdefs.h:

test.cpp
Note: including file: d:\Visual Studio 8\VC\INCLUDE\stdio.h
Note: including file:  d:\Visual Studio 8\VC\INCLUDE\crtdefs.h
Note: including file:   d:\Visual Studio 8\VC\INCLUDE\sal.h
Note: including file:   d:\Visual Studio 8\VC\INCLUDE\vadefs.h
Note: including file:  d:\Visual Studio 8\VC\INCLUDE\swprintf.inl
相關文章
相關標籤/搜索