VC 中的宏使用方法參考 MSDN: Macros (C/C++)php
__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
// MacroTest.h 編程
void PrintSourceInfo() api
{ 安全
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++
參考 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 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))
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__)))
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)
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
VC CRT 和 C 標準庫中的宏參考 MSDN: Global Constants
NULL 在 stddef.h, stdio.h, stdlib.h 等多個頭文件中定義,是地址/指針類型的 0,以下:
#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() 的返回值
在 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, double, long double) 的極限值,如最小、最大值,最小浮點差量 (epsilon) 等
參考 MSDN: Floating Limits
例子:浮點數極限值:判斷浮點數是否相等^
// 對比一個 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
數學計算經常使用的浮點數常量,如 M_PI (pi), M_E (e), M_SQRT2 (sqrt(2)) 等。這些數學常量不是標準 C/C++ 的一部分,而是 Microsoft 的擴展,使用前須要定義 _USE_MATH_DEFINES:
#define _USE_MATH_DEFINES
#include <math.h>
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")));
測試輸出,用 Ctrl + Z 產生 EOF 信號:
abc漢字 [a][b][/c][漢][字][ ]^Z read stdin: EOF
在 errno.h 中定義,是測試全局量 errno 的值,errno 在 VC 中實現爲線程安全的函數,而非全局變量。錯誤代碼以 E 打頭如 EINVAL:不合法的參數錯誤
錯誤代碼具體值參考 MSDN: errno Constants 和 errno, _doserrno, _sys_errlist, and _sys_nerr
locale 類別 (Categories),在 locale.h 中定義,如 LC_ALL、LC_CTYPE
包括全路徑與各部分路徑的限制,即 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 函數
在 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);
用於訪問相似 printf(const char* format, ...) 等變長函數參數的輔助宏,在 stdarg.h 中聲明,參考 MSDN:va_arg, va_end, va_start
在 VC CRT 中有些函數以宏和函數兩種方式實現,如 getchar(),並優先使用宏版本,
強制使用函數版的方法:
(1). 調用時給函數名加括號,如 (getchar)()
(2). 調用前,取消宏版本的定義,如 #undef getchar
兩種實現方式的比較見 MSDN: Recommendations for Choosing Between Functions and Macros
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
上面 一、二、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__ 用法:
// 針對參數不爲 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);
例子:用 __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
};
例子:用 __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
}
註釋性宏,便是否使用它們不影響編譯結果,一般定義爲空
目的:
(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
);
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)
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)
標記沒有使用的參數、變量輔助宏^
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)))
調用規範/約定參考 MSDN: Calling Conventions
Windows API 使用的調用規範名稱宏,在 WinDef.h 中定義:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#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
#define __STRINGIZE(x) # x
#define _STRINGIZE(x) __STRINGIZE(x)
#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_WIDE() 的定義:
// crtdefs.h
#ifndef _CRT_STRINGIZE
#define __CRT_STRINGIZE(_Value) #_Value
#define _CRT_STRINGIZE(_Value) __CRT_STRINGIZE(_Value)
#endif
#ifndef _CRT_WIDE
#define __CRT_WIDE(_String) L ## _String
#define _CRT_WIDE(_String) __CRT_WIDE(_String)
#endif
1. 若是 _STRINGIZE() 的參數是宏,那麼宏表明的實際值也將被展開,即嵌套展開
例子:用 STRINGIZE 查看宏的展開結果^
查看某個宏在當前編譯配置 (Debug/Release, ANSI/Unicode) 下,實際表示的東西,如某個 _t 系列函數、Windows API 究竟表示哪一個函數,能夠利用 _STRINGIZE():
// 將輸出實際的行號、數字,而非字符串 "__LINE__"、"MAX_PATH"
_tprintf(_T("Line: %s\n"), _TSTRINGIZE(__LINE__));
_tprintf(_T("MAX_PATH: %s\n"), _TSTRINGIZE(MAX_PATH));
// 判斷宏的當前值、調用了哪一個版本的 _t 系列函數、Windows API
_tprintf(_T("_DEBUG: %s, _UNICODE: %s\n"), _TSTRINGIZE(_DEBUG), _TSTRINGIZE(_UNICODE));
_tprintf(_T("_tprintf: %s\n"), _TSTRINGIZE(_tprintf));
_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() 括號中的東西加引號而已,以下:
// 非 const、其它內部類型 double、char,結果都同樣
const int val = 260;
// 枚舉常量
enum MUSIC_STATE
{
ST_STOP,
ST_PLAY,
ST_PAUSE,
ST_BUTT
};
// 自定義結構、類
ClassTest obj;
// 函數
void func(int a);
// 下面輸出 _TSTRINGIZE() 括號中名字加上引號獲得的字符串,而非實際變量值
_tprintf(_T("int: %s, val: %s\n"), _TSTRINGIZE(int), _TSTRINGIZE(val));
_tprintf(_T("MUSIC_STATE: %s, ST_STOP: %s\n"), _TSTRINGIZE(MUSIC_STATE), _TSTRINGIZE(ST_STOP));
_tprintf(_T("ClassTest: %s, obj: %s\n"), _TSTRINGIZE(ClassTest), _TSTRINGIZE(obj));
_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
#define __CONCAT(x, y) x ## y
#define _CONCAT(x, y) __CONCAT(x, y)
## 與 # 同樣對其操做數不進行嵌套展開,因此 __CONCAT(aaa, __CONCAT(bbb, ccc)) 的展開結果是 aaa__CONCAT(bbb, ccc),而 _CONCAT(aaa, _CONCAT(bbb, ccc)) 的展開結果是 aaabbbccc。## 的結果是名字拼接,而不是字符串字面量,即不是 "aaabbbccc"
一般用 ## 操做拼接構造類型、變量、函數的名字
例子:_T() 的定義^
// tchar.h
#ifdef _UNICODE
#define __T(x) L ## x
#else
#define __T(x) x
#define _T(x) __T(x)
例子:Windows API 通用句柄類型的定義^
// winnt.h
typedef void *HANDLE;
#define DECLARE_HANDLE(name) struct name##__ { int unused; }; typedef struct name##__ *name
// 所以多數 Windows 句柄是指向樁結構的指針,如 HWND:
// windef.h
DECLARE_HANDLE (HWND);
// HWND 定義展開後是:
struct HWND__
{
int unused;
};
typedef struct HWND__ *HWND;
例子:用 ## 構造函數名^
// 音樂播放狀態常量
enum MUSIC_STATE
{
ST_STOP,
ST_PLAY,
ST_PAUSE,
ST_BUTT
};
// 音樂播放狀態結構
// 裏面有一個用於處理特定狀態的回調函數 stat_proc
typedef struct _MusicState
{
MUSIC_STATE stat;
const _TCHAR* stat_name;
int (*stat_proc)(void*);
} MusicState;
// 處理特定音樂播放狀態的函數
// 函數名的統一形式 proc_ ## stat,stat 是狀態常量的名字
int proc_ST_STOP(void*);
int proc_ST_PLAY(void*);
int proc_ST_PAUSE(void*);
// 初始化音樂播放狀態結構
#define INIT_MUSIC_STATE(stat) {stat, _TSTRINGIZE(stat), proc_ ## stat}
MusicState g_MusicState[ST_BUTT] =
{
INIT_MUSIC_STATE(ST_STOP),
INIT_MUSIC_STATE(ST_PLAY),
INIT_MUSIC_STATE(ST_PAUSE)
};
_TCHAR、_T()、_t 系列函數等東西叫作 Generic-Text Mapping,即便用宏進行統一字符類型編寫,在不一樣的字符集編碼工程配置 ANSI/UNICODE/MBCS 下替換爲不一樣的實際函數或類型,參考 MSDN:Generic-Text Mappings,Using Generic-Text Mappings, Using TCHAR.H Data Types with _MBCS
工程的字符集配置的宏定義:
ANSI (SBCS, ASCII): _UNICODE 和 _MBCS 均未定義,使用 char 單字節字符集編碼
UNICODE: _UNICODE 定義,使用 wchar_t 寬字符集編碼,VC 默認 wchar_t 2 字節
MBCS: _MBCS 定義,使用 char 變長字符集編碼,一個字符佔一個或多個 char
根據 _UNICODE、_MBCS 的定義,調用 ANSI/UNICODE/MBCS 不一樣字符集版本的 CRT 函數,或產生字面量,多在 tchar.h 中聲明。_t 字符操做函數參考 MSDN:String Manipulation (CRT)
根據 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 時纔會分配內存):
const char* DEF_FILENAME = "default.txt";
enum BUF_SIZE
{
BUF_SIZE_SMALL = 64,
BUF_SIZE_MEDIUM = 256,
BUF_SIZE_LARGE = 1024
};
另外,在 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). 宏體爲多行語句,若是放到判斷語句中,而且不加 {} 包起來,只有第一句在判斷語句下執行,其它在判斷語句外,以下例:
#define SWAP(v1, v2, tmp) \
tmp = v1; \
v1 = v2; \
v2 = tmp;
// 用戶代碼
if (condition)
SWAP(a, b, t); // 邏輯問題
if (condition) {
SWAP(a, b, t); // OK
}
解決方法:定義宏時用 {} 或 do {} while(0) 包起來,以下:
#define SWAP(v1, v2, tmp) \
do { \
tmp = v1; \
v1 = v2; \
v2 = tmp; \
} 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 解決。以下:
class TestClass1
{
private:
int m_Val;
// private 限制對宏 MACRO_DEF_VAL 不起做用
#define MACRO_DEF_VAL 128
public:
static const int CONST_DEF_VAL = 128;
enum { ENUM_DEF_VAL = 128 };
};
class TestClass2
{
private:
int m_Val;
// 產生 C4005 警告:MACRO_DEF_VAL 被重複定義
#define MACRO_DEF_VAL 256
public:
static const int CONST_DEF_VAL = 256;
enum { ENUM_DEF_VAL = 256 };
};
// 用戶代碼
// 宏 MACRO_DEF_VAL 是全局的,不能寫爲 TestClass1::MACRO_DEF_VAL
_tprintf(_T("TestClass1: %d, %d, %d\n"), MACRO_DEF_VAL, TestClass1::CONST_DEF_VAL, TestClass1::ENUM_DEF_VAL);
_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
例子:註釋大量代碼^
#if 0
XXXXXXXXX
#endif
#if FALSE
XXXXXXXXX
#endif
例子:MFC 中的調試版代碼示例^
見上文「MFC 的調試版 new」
AssertValid() 參考 MSDN: MFC ASSERT_VALID and CObject::AssertValid
// FrameWindow、Doc、View 等類都可覆蓋如下用於調試的診斷函數
// 而後能夠用 ASSERT_VALID() 診斷其對象狀態是否有效
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
// Release 版本的 GetDocument() 是 inline 的
#ifndef _DEBUG
inline CMyDoc* CMyView::GetDocument() const
{
return reinterpret_cast<CMyDoc*>(m_pDocument);
}
#endif
例子:DLL 工程導出符號^
工程 DllProj 中導出符號(變量、函數、類)的方法:在工程中定義宏 DLLPROJ_EXPORTS (/D "DLLPROJ_EXPORTS"),並在使用工程中保證沒有定義 DLLPROJ_EXPORTS
DllProj 導出符號的聲明文件 DllProj.h 以下,在使用工程中 #include 該文件:
// DllProj.h
#ifndef _DLLPROJ_H_
#define _DLLPROJ_H_
#ifdef DLLPROJ_EXPORTS
#define DLLPROJ_API __declspec(dllexport)
#else
#define DLLPROJ_API __declspec(dllimport)
#endif
#ifdef __cplusplus
#define EXTERN_C extern "C"
#define EXTERN_C_BEGIN extern "C" {
#define EXTERN_C_END }
#else // __cplusplus defined
#define EXTERN_C extern
#define EXTERN_C_BEGIN
#define EXTERN_C_END
#endif // __cplusplus NOT defined
// 導出類
class DLLPROJ_API TestClass
{
public:
TestClass();
};
// 導出全局變量,以 C 的連接方式(修飾名、調用約定)
EXTERN_C DLLPROJ_API int g_TestVal;
// 導出函數
DLLPROJ_API int TestFunc();
#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 以及使用方法以下:
// undefine_WXDLLIMPEXP_CORE.h
// 不要用 #pragma once 等包含一次技巧,由於一個源文件中可能有多個
// BEGIN_DECLARE_EVENT_TYPES 自定義事件塊,這時要屢次包含本文件
#ifdef WXDLLIMPEXP_CORE
# define REMOVE_WXDLLIMPEXP_CORE
# undef WXDLLIMPEXP_CORE // 先取消 WXDLLIMPEXP_CORE 定義
# define WXDLLIMPEXP_CORE // 再將其定義爲空
#endif
// redefine_WXDLLIMPEXP_CORE.h
// 不要用 #pragma once 等包含一次技巧
#ifdef REMOVE_WXDLLIMPEXP_CORE
# undef WXDLLIMPEXP_CORE
// 如下塊拷貝自 wx-2.8.10 dlimpexp.h,用於恢復 WXDLLIMPEXP_CORE 的原有定義
// BEGIN
# ifdef WXMAKINGDLL_CORE
# define WXDLLIMPEXP_CORE WXEXPORT
# define WXDLLIMPEXP_DATA_CORE(type) WXEXPORT type
# elif defined(WXUSINGDLL)
# define WXDLLIMPEXP_CORE WXIMPORT
# define WXDLLIMPEXP_DATA_CORE(type) WXIMPORT type
# else /* not making nor using DLL */
# define WXDLLIMPEXP_CORE
# define WXDLLIMPEXP_DATA_CORE(type) type
# endif
// END
# undef REMOVE_WXDLLIMPEXP_CORE
#endif
// 用戶代碼
// 定義 WXDLLIMPEXP_CORE 爲空
#include <undefine_WXDLLIMPEXP_CORE.h>
// 自定義事件
BEGIN_DECLARE_EVENT_TYPES()
DECLARE_EVENT_TYPE(wxEVENT_TEST_TRIGGERED, wxID_ANY)
// DECLARE_LOCAL_EVENT_TYPE(wxEVENT_TEST_TRIGGERED, wxID_ANY) // 用這個不會有 BUG
END_DECLARE_EVENT_TYPES()
// 重定義 WXDLLIMPEXP_CORE
#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、第三方庫的頭文件,以及工程全局的設置和名字符號,在從新編譯工程時,這些代碼便不會從新編譯,以加快編譯速度
以 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 代碼^
////////////////////////////////////////////////////////////////////////////////
///
/// @file stdafx.h
/// @brief Windows 標準預編譯頭文件
///
/// 將標準庫、運行時庫、基本庫、Windows API、第三方庫的頭文件在這裏包含,生成
/// 預編譯頭文件 MFCBasic.pch
///
/// 若是不修改 stdafx.h,增量編譯時便不會從新編譯 stdafx.h 中包含的頭文件,這樣
/// 加快了編譯速度
///
/// @version <version>
/// @author <author>
/// @date 2011-07
///
/// Copyright (c) 2011, <company>
/// All rights reserved.
///
////////////////////////////////////////////////////////////////////////////////
// 典型的「只包含一次」條件編譯技巧
// VC cl 編譯器版本 10 以上 (_MSC_VER > 1000) 也可使用 #pragma once 指令
#ifndef _STDAFX_H_
#define _STDAFX_H_
// 排除不多使用的 Windows 頭文件
#define WIN32_LEAN_AND_MEAN // 適用於 Windows API
#ifndef VC_EXTRALEAN
#define VC_EXTRALEAN // 適用於 MFC
#endif
// 指定目標系統和環境 (Windows, IE) 的版本號
#ifndef WINVER
#define WINVER 0x0501 // 目標系統具備 Windows XP 及以上特性
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0501 // 目標系統具備 Windows XP 及以上特性
#endif
#ifndef _WIN32_WINDOWS
#define _WIN32_WINDOWS 0x0410 // 目標系統具備 Windows 98 及以上特性
#endif
#ifndef _WIN32_IE
#define _WIN32_IE 0x0600 // 目標系統具備 IE 6.0 及以上特性
#endif
////////////////////////////////////////////////////////////////////////////////
/// Include Header
////////////////////////////////////////////////////////////////////////////////
// C 標準庫與運行時庫 (CRT)
// BEGIN
//
#define _CRT_SECURE_NO_DEPRECATE // 使用廢棄 (deprecated) 的 CRT 函數時,不產生編譯警告
#define _CRT_SECURE_NO_WARNINGS // 典型的廢棄函數有不帶緩衝區大小檢查的 strcpy()、strcat()、sprintf() 等
#include <stdlib.h>
#include <tchar.h>
#include <crtdbg.h>
#include <string.h>
//
// END
// C++ 標準庫
// BEGIN
//
#include <exception>
#include <typeinfo>
//
// END
// MFC 庫
// BEGIN
//
#ifndef _SECURE_ATL
#define _SECURE_ATL 1 // ATL/MFC 的安全設置
#endif
#define _ATL_CSTRING_EXPLICIT_CONSTRUCTORS // 使 ATL/MFC 的 CString 具備顯式地構造函數 (explicit)
#define _AFX_ALL_WARNINGS // 打開 MFC 的全部警告,包括通常能夠安全忽略的警告
#include <afxwin.h> // MFC 核心和標準支持
#include <afxext.h> // MFC 擴展支持
#ifndef _AFX_NO_OLE_SUPPORT
#include <afxdtctl.h> // MFC 的 IE 4 通用控件支持
#endif
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h> // MFC 的 Windows 通用控件支持
#endif
//
// END
// Windows API
// BEGIN
//
// #include <Windows.h> // 使用 MFC 庫時不要包含 Windows.h,MFC 頭文件中已包含
#include <Winsock2.h>
//
// END
// Windows 通用控件 ComCtl32.dll 版本 6.0 的內嵌 manifest
// BEGIN
//
#ifdef _UNICODE
#if defined _M_IX86
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_IA64
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='ia64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#elif defined _M_X64
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='amd64' publicKeyToken='6595b64144ccf1df' language='*'\"")
#else
#pragma comment(linker, "/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#endif
#endif // _UNICODE
//
// END
#endif // _STDAFX_H_
VC 支持的預處理指令參考 MSDN: Preprocessor Directives
#error 產生 fatal error,編譯器輸出 #error 後的提示文本。指示該源文件必需使用 C++ 方式編譯,以下:
// test.cpp
#ifndef __cplusplus
#error MUST use C++ compilation
#endif
以 C 語言方式編譯上面源文件 (/Tc test.cpp) 時報錯:
fatal error C1189: #error : MUST use C++ compilation
#line 改變 __FILE__ 和 __LINE__ 的取值,例如:
// 實際文件名:test_01.cpp
#line 1 "test_02.cpp"
_tprintf(_T("File: %s, Line: %d\n"), _T(__FILE__), __LINE__); // 實際第 200 行
輸出:
File: test_02.cpp, Line: 1
沒有做用的合法預處理指令行正則表達式:[\t ]*#[\t ]*
#pragma 是一組編譯器特定的預處理指令,每種編譯器的 #pragma 的子指令都有所不一樣。VC 的 #pragma 指令參考 MSDN: Pragma Directives and the __Pragma Keyword
經常使用的 #pragma 指令以下:
對頭文件只包含一次,以下:
// test.h
#if _MSC_VER > 1000
#pragma once
#endif
// 頭文件中的代碼
它和傳統的 #ifndef 只包含一次技巧的功能相同:
// test.h
#ifndef _TEST_H_
#define _TEST_H_
// 頭文件中的代碼
#endif // _TEST_H_
在源文件中屢次 #include 包含 test.h 時,不會出現 redefinition 錯誤
#pragma message 在編譯過程當中,向標準輸出或 VC 的 Output 窗口打印指定消息,做用:(1) 告知程序員代碼編譯和使用的注意事項 (2) 用於查看和診斷實際的編譯代碼
例子:用 #pragma message 和 STRINGIZE 查看宏的展開結果^
例 11 是用 STRINGIZE 在運行時輸出宏的展開結果,其實在編譯時也能夠用 #pragma message 輸出,診斷編譯的實際代碼:
// 將輸出實際的行號、數字,而非字符串 "__LINE__"、"MAX_PATH"
#pragma message("Line: " _STRINGIZE(__LINE__))
#pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))
// 判斷宏的當前值、調用了哪一個版本的 _t 系列函數、Windows API
#pragma message("_DEBUG: " _STRINGIZE(_DEBUG) ", _UNICODE: " _STRINGIZE(_UNICODE))
#pragma message("_tprintf: " _STRINGIZE(_tprintf))
#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 用來解決宏命名衝突問題,以下:
// 保存來自 windef.h 的 MAX_PATH 定義
#pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))
#pragma push_macro("MAX_PATH")
// 對 MAX_PATH 進行新的定義
// 即便以前沒有定義 MAX_PATH,#undef MAX_PATH 也不會報錯
#undef MAX_PATH
#define MAX_PATH 512
#pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))
// 使用新的 MAX_PATH
// 恢復 windef.h 的 MAX_PATH 定義
#pragma pop_macro("MAX_PATH")
#pragma message("MAX_PATH: " _STRINGIZE(MAX_PATH))
#pragma message 輸出以下:
MAX_PATH: 260 MAX_PATH: 512 MAX_PATH: 260
例子:
// 禁用 C450七、C4034 警告(VC 的 warning 編號從 C4001 開始)
// 只報告一次 C4385 警告
// 將 C4164 警告做爲編譯錯誤
#pragma warning(disable: 4507 34; once: 4385; error: 164)
#pragma warning(push) // 保存當前的警告設置:全局警告級別和 disable 的
#pragma warning(disable: 4705) // 禁用某些警告
#pragma warning(disable: 4706)
#pragma warning(disable: 4707)
// 會產生 C470五、C470六、C4707 警告的代碼
#pragma warning(pop) // 恢復保存的警告設置
#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 以下:
// DllProj.h
#ifndef _DLLPROJ_H_
#define _DLLPROJ_H_
#ifdef DLLPROJ_EXPORTS
#define DLLPROJ_API __declspec(dllexport)
#else
#pragma comment(lib, "DllProj.lib") // 指示在導入符號時連接 DllProj.lib
#define DLLPROJ_API __declspec(dllimport)
#endif
// 省略代碼
#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") 設置可讀寫、共享區段
#pragma comment(linker, "/SECTION:.mydata,RWS")
#pragma data_seg(".mydata")
// .mydata 區段中的變量定義
#pragma data_seg()
區段屬性和標準區段名參考 /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 建立可讀寫、共享區段
#pragma section(".mydata", read, write, shared) // 在 .obj 中新建可讀寫、共享區段 .mydata
// #pragma comment(linker, "/SECTION:.mydata,RWS") // 做用與上相似:在連接時調整區段屬性
#pragma data_seg(".mydata") // 將如下初始化數據放置到 .mydata
// .mydata 區段中的變量定義
#pragma data_seg() // 恢復默認的初始化數據區段 .data
__declspec(allocate(".mydata")) // 用 __declspec(allocate) 放置數據到區段
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 連接約定函數
extern "C" void TestFunc_1(); // 必需在 .mycode 以前有函數聲明
extern "C" void TestFunc_2(); // 而且是 C 連接約定的
#pragma section(".mycode", read, execute, nopage) // 創建可執行、非分頁區段
// #pragma comment(linker, "/SECTION:.mycode,RE!P") // 做用與上相似:在連接時調整區段屬性
#pragma alloc_text(".mycode", TestFunc_1, TestFunc_2) // 將指定函數放到 .mycode 中
void TestFunc_1()
{
// TestFunc_1 函數體
}
void TestFunc_2()
{
// TestFunc_2 函數體
}
#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 放置函數
#pragma section(".mycode", read, execute, nopage) // 創建可執行、非分頁區段
// #pragma comment(linker, "/SECTION:.mycode,RE!P") // 做用與上相似:在連接時調整區段屬性
#pragma code_seg(".mycode") // 將如下函數放到 .mycode 區段中
void TestFunc_1()
{
// TestFunc_1 函數體
}
void TestFunc_2()
{
// TestFunc_2 函數體
}
#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 放置只讀常量數據
例子:自定義區段和數據性質衝突
如下錯誤編譯器不會報錯,但實際沒有放置到指望的區段中
int g_Var1 = 1; // 放置到 .data 中
int g_Var2 = 0; // 放置到 .obj 的 .bss 中,連接時合併到 .data
int g_Var3; // 同上
const int g_Var4 = 1; // 編碼到代碼中,沒有放置到 .rdata 中
const char g_szVar5[]= "foo"; // 放置到 .rdata 中
#pragma const_seg(".myrdata")
const char g_szVar6[]= "foo"; // 放置到 .myrdata 中
#pragma const_seg()
#pragma bss_seg(".mybss")
int g_Var7 = 1; // 錯誤:放置到 .data 中
int g_Var8 = 0; // 錯誤:放置到 .obj 的 .bss 中,連接時合併到 .data
int g_Var9; // 正確放置到 .mybss 中
#pragma bss_seg()
#pragma data_seg(".mydata")
int g_Var10 = 0; // 正確放置到 .mydata 中
int g_Var11; // 錯誤:放置到 .obj 的 .bss 中,連接時合併到 .data
#pragma data_seg()
設置 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 設置成員字節對齊
// x86 32bit
#pragma pack(4) // 用一對 #pragma pack(4) | #pragma pack()
// #pragma pack(push, 4) // 用一對 #pragma pack(push, 4) | #pragma pack(pop)
struct TestStruct
{
double a; // sizeof(double) = 8
int b; // sizeof(int) = 4
}; // sizeof(TestStruct) = 12
// #pragma pack(4) 會一直做用,直到改變 pack
#pragma pack() // 恢復編譯選項 /Zp 設置的字節對齊
// #pragma pack(pop) // 恢復 #pragma pack(push, 4) 以前的字節對齊
inline 函數修飾,參考 MSDN: inline, __inline, __forceinline
inline 函數編譯優化選項,參考 MSDN: /Ob (Inline Function Expansion)
#pragma auto_inline 禁用和啓用 auto-inline^
例子:
使用 /O2 編譯優化選項,含 /Ob2:啓動 auto-inline
#pragma auto_inline(off)
int simple_func(int a) // 不會 inline 化
inline // #pragma auto_inline(off) 不會做用於顯式指定的 inline 函數
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 次
編譯優化選項 /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
#pragma optimize("pt", on) // 對下面的代碼使用 fp:precise, /Ot 優化
// 函數定義
#pragma optimize("", off) // 關閉上次 #pragma optimize 指定的優化
#pragma optimize("", on) // 恢復到編譯器 /O 選項指定的優化
#pragma intrinsic 使用 intrinsic 函數^
使用 intrinsic 函數編譯選項 /Oi,參考 MSDN: /Oi (Generate Intrinsic Functions)
#pragma intrinsic,參考 MSDN: intrinsic
#include <string.h>
// 使用 /Oi 編譯選項
#pragma intrinsic(memcpy)
#pragma function 使用普通函數^
和 #pragma intrinsic 對應,改變 /Oi 選項或以前的 #pragma intrinsic 設置,使用指定函數名的普通函數版本
#pragma deprecated 用來聲明廢棄的函數、類型、宏,編譯器產生 C4995 警告
__declspec(deprecated) 修飾也可用來聲明廢棄的函數、類型,編譯器產生 C4996 警告
例子:
// 使用 #pragma deprecated
// BEGIN
//
#pragma deprecated(OldClass)
class OldClass1;
#pragma deprecated(test_func1)
void old_func1();
//
// END
// 使用 __declspec(deprecated)
// BEGIN
#define DEPRECATED_WILL_RMOVED "** will be removed in next version **"
// deprecated() 中的字符串不是必需的,若是有,會在警告時輸出
__declspec(deprecated(DEPRECATED_WILL_RMOVED)) void old_func2();
// 注意 __declspec(deprecated) 修飾 class 時的位置
class __declspec(deprecated) OldClass2;
//
// END
void test()
{
old_func1(); // 產生 C4995 警告
OldClass1 obj; // 產生 C4995 警告
old_func2(); // 產生 C4996 警告,並輸出 "** will be removed in next version **"
OldClass2(); // 產生 C4996 警告
}
指令形式:
#pragma omp omp_directive
用於多線程、併發編程的 OpenMP 指令,子指令 omp_directive 參考 MSDN: OpenMP Directives
標記一整塊代碼,在 VC 編輯器可摺疊成一行 (+) 和展開,見 VC 的 Edit->Outlining 菜單
VC Outlining 經常使用快捷鍵:
Ctrl + M, Ctrl + L: 摺疊或展開全部的代碼塊
Ctrl + M, Ctrl + M: 摺疊或展開光標所在的代碼塊
#pragma region FuncTestCode // 摺疊成一行後,(+) 後顯示的名字
// 這裏是一整塊代碼
#pragma endregion Test Code of Func() // 摺疊後在名字後顯示的註釋
#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(<stdio.h>, <newstdio.h>)
#pragma include_alias("api.h", "test\api.h")
#include <stdio.h>
#include "api.h"
/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: 預處理源文件,結果輸出到標準輸出,去掉註釋,在 #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:
#include <stdio.h>
int main()
{
#ifdef _DEBUG
printf("Debug config\n");
#else
printf("Release config\n");
#endif
// MARK: TESTMACRO value
printf("TESTMACRO: %d\n", TESTMACRO);
return 0;
}
預處理編譯命令:
cl /P /C /DTESTMACRO test.cpp
預處理輸出到 test.i:
#line 1 "test.cpp"
#line 1 "d:\\Visual Studio 8\\VC\\INCLUDE\\stdio.h"
// #line 中 stdio.h 的路徑由實際 VC 安裝路徑而定
// 這裏省略 stdio.h 展開後的大量代碼
#line 706 "d:\\Visual Studio 8\\VC\\INCLUDE\\stdio.h"
#line 2 "test.cpp"
int main()
{
printf("Release config\n");
#line 10 "test.cpp"
// MARK: TESTMACRO value
printf("TESTMACRO: %d\n", 1);
return 0;
}
例子:過濾查看預處理展開結果^
用這種方法能夠查看編譯過程當中,實際的宏展開、預處理結果
以上面的 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; }
輸出源文件的 #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