1. 程式人生 > >C/C++ 中巨集與預處理使用方法大全 (VC)

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

C/C++ 中的巨集 (#define) 與預處理 (#if/#ifdef/#pragma) 的使用方法大全、使用技巧

開發環境:VC 2005

關鍵字:巨集, 預定義巨集, 預處理, 預編譯頭, VC, #pragma, 編譯選項, 程式區段

RTFM: Read The F__king Manual/MSDN

目錄

C/C++ 預定義巨集^

__LINE__: 當前原始檔的行號,整數
__FILE__: 當前原始檔名,char 字串,使用 /FC 選項產生全路徑
__DATE__: 當前編譯日期,char 字串,格式 Aug 28 2011
__TIME__: 當前編譯時間,char 字串,格式 06:43:59
__STDC__

: 整數 1,表示相容 ANSI/ISO C 標準,配合 #if 使用
__TIMESTAMP__: 最後一次修改當前檔案的時間戳,char 字串,格式 Sun Aug 28 06:43:57 2011
__cplusplus: 以 C++ 方式而非 C 語言方式編譯時定義,VC 2005 中定義為 199711L,配合 #ifdef 使用

例子:C/C++ 預定義巨集的取值^

  1. // MacroTest.h
  2. void PrintSourceInfo() 
  3.     const _TCHAR* pszstdc; 
  4.     const _TCHAR* pszcpp; 
  5. #if __STDC__
  6.     pszstdc = _T("YES"); 
  7. #else
  8.     pszstdc = _T("NO"); 
  9. #endif
  10. #ifdef __cplusplus
  11.     pszcpp = _T("YES"); 
  12. #else
  13.     pszcpp = _T("NO"); 
  14. #endif
  15.     _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s, ANSI/ISO C: %s, C++: %s\n"), 
  16.              _T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__), pszstdc, pszcpp); 
  17. // 巨集化的 PrintSourceInfo()
  18. #define PRINT_SOURCE_INFO() \
  19.     _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s\n"), \ 
  20.              _T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__)); 
// MacroTest.h
void PrintSourceInfo()
{
    const _TCHAR* pszstdc;
    const _TCHAR* pszcpp;

#if __STDC__
    pszstdc = _T("YES");
#else
    pszstdc = _T("NO");
#endif

#ifdef __cplusplus
    pszcpp = _T("YES");
#else
    pszcpp = _T("NO");
#endif

    _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s, ANSI/ISO C: %s, C++: %s\n"),
             _T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__), pszstdc, pszcpp);
}

// 巨集化的 PrintSourceInfo()
#define PRINT_SOURCE_INFO() \
    _tprintf(_T("File: %s, Line: %d, Date: %s, Time: %s, Timestamp: %s\n"), \
             _T(__FILE__), __LINE__, _T(__DATE__), _T(__TIME__), _T(__TIMESTAMP__));

MacroTest.h 中定義函式 PrintSourceInfo() 和 PRINT_SOURCE_INFO(),在 MacroTest.cpp include=> MacroTest.h,並呼叫它們

輸出結果

(1). 使用函式 PrintSourceInfo(),無論 Debug/Release 方式編譯,無論是否 inline 化 PrintSourceInfo(),輸出結果相同,均是 MacroTest.h 的資訊:

File: d:\source\macrotest\macrotest.h, Line: 64, Date: Aug 28 2011, Time: 06:43:59, Timestamp: Sun Aug 28 06:43:57 2011, ANSI/ISO C: NO, C++: YES

(2). 使用巨集 PRINT_SOURCE_INFO(),Debug/Release 方式編譯輸出結果大致相同,均是 MacroTest.cpp 的資訊,只是 Debug 輸出的 __FILE__ 是全路徑,而 Release 輸出的是相對路徑:

File: d:\source\macrotest\macrotest.cpp, Line: 14, Date: Aug 28 2011, Time: 07:42:30, Timestamp: Sun Aug 28 07:38:25 2011

說明

(1). __FILE__、__DATE__、__TIME__ 是 char 字串,而不是 wchar_t 寬字元字串,需配合 _T()、_t 系列函式使用

(2). 如果在函式 PrintSourceInfo() 中使用巨集,則 __FILE__、__LINE__、__TIME__ 等表示的是 PrintSourceInfo() 所在檔案,即例 1 中的 MacroTest.h 的資訊;如果在巨集 PRINT_SOURCE_INFO() 中使用巨集,因為巨集 PRINT_SOURCE_INFO() 巢狀展開的緣故,__FILE__ 等表示的是 PRINT_SOURCE_INFO() 展開所在檔案,即 MacroTest.cpp 的資訊

(3). 無論使用 PrintSourceInfo() 還是 PRINT_SOURCE_INFO(),__LINE__ 總是檔案 .h/.cpp 的固有行號,而非 [MacroTest.cpp include=> MacroTest.h] 預處理展開後的行號

(4). 在 VC 2005 中,上述編譯方式下沒有定義 __STDC__,要使 __STDC__ = 1,應同時滿足以下條件:

  • (a). 以 C 方式編譯
  • (b). 使用編譯選項 /Za,表示禁止 Microsoft C/C++ 語言擴充套件,從而相容 ANSI C/C++

C/C++ 預定義巨集用途:診斷與除錯輸出^

參考 VC CRT 和 MFC 的程式碼,注意:需要在巨集中使用 __FILE__、__LINE__,原因見上面“說明 (2)”

CRT 的診斷與除錯輸出:assert, _ASSERT/_ASSERTE, _RPTn/_RPTFn/_RPTWn/_RPTFWn^

CRT 的診斷巨集 assert()、_ASSERT()/_ASSERTE()

  1. // assert.h
  2. _CRTIMP void __cdecl _wassert(__in_z constwchar_t * _Message, __in_z constwchar_t *_File, __in unsigned _Line); 
  3. #define assert(_Expression) (void)( (!!(_Expression)) || (_wassert(_CRT_WIDE(#_Expression), _CRT_WIDE(__FILE__), __LINE__), 0) )
  4. // crtdbg.h
  5. #define _ASSERT_EXPR(expr, msg) \
  6.         (void) ((!!(expr)) || \ 
  7.                 (1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \ 
  8.                 (_CrtDbgBreak(), 0)) 
  9. #ifndef _ASSERT
  10. #define _ASSERT(expr)   _ASSERT_EXPR((expr), NULL)
  11. #endif
  12. #ifndef _ASSERTE
  13. #define _ASSERTE(expr)  _ASSERT_EXPR((expr), _CRT_WIDE(#expr))
  14. #endif
// assert.h

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

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

// crtdbg.h

#define _ASSERT_EXPR(expr, msg) \
        (void) ((!!(expr)) || \
                (1 != _CrtDbgReportW(_CRT_ASSERT, _CRT_WIDE(__FILE__), __LINE__, NULL, msg)) || \
                (_CrtDbgBreak(), 0))

#ifndef _ASSERT
#define _ASSERT(expr)   _ASSERT_EXPR((expr), NULL)
#endif

#ifndef _ASSERTE
#define _ASSERTE(expr)  _ASSERT_EXPR((expr), _CRT_WIDE(#expr))
#endif

CRT 的除錯輸出巨集 _RPTn()/_RPTFn(),n: 0 ~ 5
_RPTWn()/_RPTFWn() 是寬字元版

  1. // crtdbg.h
  2. #define _RPT_BASE(args) \
  3.         (void) ((1 != _CrtDbgReport args) || \ 
  4.                 (_CrtDbgBreak(), 0)) 
  5. #define _RPTF0(rptno, msg) \
  6.         _RPT_BASE((rptno, __FILE__, __LINE__, NULL, "%s", msg)) 
// crtdbg.h

#define _RPT_BASE(args) \
        (void) ((1 != _CrtDbgReport args) || \
                (_CrtDbgBreak(), 0))

#define _RPTF0(rptno, msg) \
        _RPT_BASE((rptno, __FILE__, __LINE__, NULL, "%s", msg))

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

MFC 的診斷巨集 ASSERT()/VERIFY()、ASSERT_VALID()

  1. // afx.h
  2. #define ASSERT(f)          DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0)))
  3. #define ASSERT_VALID(pOb)  DEBUG_ONLY((::AfxAssertValidObject(pOb, THIS_FILE, __LINE__)))
// afx.h

#define ASSERT(f)          DEBUG_ONLY((void) ((f) || !::AfxAssertFailedLine(THIS_FILE, __LINE__) || (AfxDebugBreak(), 0)))
#define ASSERT_VALID(pOb)  DEBUG_ONLY((::AfxAssertValidObject(pOb, THIS_FILE, __LINE__)))

MFC 的除錯輸出巨集 TRACE()/TRACEn(),n: 0 ~ 3

  1. // atltrace.h
  2. #ifndef ATLTRACE
  3. #define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)
  4. #define ATLTRACE2 ATLTRACE
  5. #endif
  6. // afx.h
  7. #include <atltrace.h>
  8. #define TRACE ATLTRACE
  9. #define THIS_FILE          __FILE__
  10. #define VERIFY(f)          ASSERT(f)
  11. #define DEBUG_ONLY(f)      (f)
  12. #define TRACE0(sz)              TRACE(_T("%s"), _T(sz))
  13. #define TRACE1(sz, p1)          TRACE(_T(sz), p1)
  14. #define TRACE2(sz, p1, p2)      TRACE(_T(sz), p1, p2)
  15. #define TRACE3(sz, p1, p2, p3)  TRACE(_T(sz), p1, p2, p3)
// atltrace.h

#ifndef ATLTRACE
#define ATLTRACE ATL::CTraceFileAndLineInfo(__FILE__, __LINE__)
#define ATLTRACE2 ATLTRACE
#endif

// afx.h

#include <atltrace.h>
#define TRACE ATLTRACE

#define THIS_FILE          __FILE__
#define VERIFY(f)          ASSERT(f)
#define DEBUG_ONLY(f)      (f)

#define TRACE0(sz)              TRACE(_T("%s"), _T(sz))
#define TRACE1(sz, p1)          TRACE(_T(sz), p1)
#define TRACE2(sz, p1, p2)      TRACE(_T(sz), p1, p2)
#define TRACE3(sz, p1, p2, p3)  TRACE(_T(sz), p1, p2, p3)

MFC 的除錯版 new^

  1. // afx.h
  2. void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine); 
  3. #define DEBUG_NEW new(THIS_FILE, __LINE__)
  4. // 使用者程式碼
  5. // 除錯版 new
  6. #ifdef _DEBUG
  7. #define new DEBUG_NEW
  8. #endif
// afx.h

void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)

// 使用者程式碼

// 除錯版 new
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CRT 和 C 標準庫中的巨集^

NULL 空指標^

NULL 在 stddef.h, stdio.h, stdlib.h 等多個頭檔案中定義,是地址/指標型別的 0,如下:

  1. #ifdef __cplusplus
  2. #define NULL    0
  3. #else
  4. #define NULL    ((void *)0)
  5. #endif
#ifdef __cplusplus
#define NULL    0
#else
#define NULL    ((void *)0)
#endif

C++ 中的 0 是型別自動的,所以用 0 定義 NULL;而 C 中 0 是確定的 int 型別,所以需要強制

C++ 中,當 NULL 的相關運算元,如:對比操作 ptr == NULL,或函式的形參是指標型別時,或者能夠“從指標型別隱式轉換”時,0 被自動轉換為指標型別

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

  1. // baby pointer wrapper
  2. class Pointer 
  3. public
  4.     // 非 explicit 建構函式,說明 Pointer 可以從指標型別 void* 隱式轉換
  5.     Pointer(void* p) : m_Ptr(p) 
  6.     {} 
  7.     bool IsNull() const
  8.     { 
  9.         return (m_Ptr == NULL); 
  10.     } 
  11. private
  12.     void*    m_Ptr; 
  13. }; 
  14. // 形參可以從指標型別 void* 隱式轉換
  15. void TestPointer(Pointer ptr) 
  16.     _tprintf(_T("ptr is %sNULL\n"), ptr.IsNull() ? _T("") : _T("NOT ")); 
  17. // 使用者程式碼
  18. TestPointer(0);         // OK,0 是型別自動的,0 被自動轉換為 void*,再次隱式轉換為 Pointer
  19. TestPointer(NULL);      // OK,NULL 就是 0,同上
  20. TestPointer(1);         // Error,C++ 中 1 不同於 0,它是確定的 int 型別,
  21.                         // 只能提升轉換到 float/double 型別,不能自動轉換為指標
  22. TestPointer((int*)1);   // OK,強制轉換 1 為 int*,int* 自動轉換為 void*,再次隱式轉換為 Pointer
  23.                         // 注意:void* 到 int* 不能自動轉換,需要強制,參考 malloc() 的返回值
// baby pointer wrapper
class Pointer
{
public:
    // 非 explicit 建構函式,說明 Pointer 可以從指標型別 void* 隱式轉換
    Pointer(void* p) : m_Ptr(p)
    {}

    bool IsNull() const
    {
        return (m_Ptr == NULL);
    }

private:
    void*    m_Ptr;
};

// 形參可以從指標型別 void* 隱式轉換
void TestPointer(Pointer ptr)
{
    _tprintf(_T("ptr is %sNULL\n"), ptr.IsNull() ? _T("") : _T("NOT "));
}

// 使用者程式碼
TestPointer(0);         // OK,0 是型別自動的,0 被自動轉換為 void*,再次隱式轉換為 Pointer
TestPointer(NULL);      // OK,NULL 就是 0,同上
TestPointer(1);         // Error,C++ 中 1 不同於 0,它是確定的 int 型別,
                        // 只能提升轉換到 float/double 型別,不能自動轉換為指標
TestPointer((int*)1);   // OK,強制轉換 1 為 int*,int* 自動轉換為 void*,再次隱式轉換為 Pointer
                        // 注意:void* 到 int* 不能自動轉換,需要強制,參考 malloc() 的返回值

limits.h 整數型別常量^

在 limits.h 中定義,定義了各種 int 型別 (unsigned, char, short, long, __int64) 的最小、最大值,如 SCHAR_MAX (signed char MAX)、UCHAR_MAX (unsigned char MAX)、USHRT_MAX (unsigned short MAX) 等。編譯時,如果 int 字面量超出這些範圍,會編譯出錯

float.h 浮點型別常量^

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

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

  1. // 對比一個 double 是否為 0
  2. inline
  3. bool double_equal0(double n) 
  4.     return (n >= 0 ? n < DBL_MIN : n > -DBL_MIN); 
  5. // 對比兩個 double 是否相等
  6. inline
  7. bool double_equal(double l, double r) 
  8.     return (l >= r ? l - r < DBL_EPSILON : r - l < DBL_EPSILON); 
  9. // 列印函式的結果
  10. #define TEST_BOOL_FUNC(func) _tprintf(_T("%s: %s\n"), _TSTRINGIZE(func), func ? _T("TRUE") : _T("FALSE"))
  11. // 使用者程式碼
  12. // 對比 double 是否為 0 時,double_equal0() 更精確
  13. // 對比兩個 double 是否相等時,最好用 double_equal()
  14. TEST_BOOL_FUNC(double_equal0(0));                       // TRUE
  15. TEST_BOOL_FUNC(double_equal0(DBL_EPSILON));             // FALSE
  16. TEST_BOOL_FUNC(double_equal0(-DBL_EPSILON));            // FALSE
  17. TEST_BOOL_FUNC(double_equal0(DBL_MIN));                 // FALSE
  18. TEST_BOOL_FUNC(double_equal0(-DBL_MIN));                // FALSE
  19. TEST_BOOL_FUNC(double_equal(0, 0));                     // TRUE
  20. TEST_BOOL_FUNC(double_equal(DBL_EPSILON, 0));           // FALSE
  21. TEST_BOOL_FUNC(double_equal(DBL_MIN, 0));               // TRUE
  22. TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_EPSILON));   // FALSE
  23. TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_EPSILON));   // FALSE
  24. TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_MIN));       // TRUE
  25. TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_MIN));       // TRUE
// 對比一個 double 是否為 0
inline
bool double_equal0(double n)
{
    return (n >= 0 ? n < DBL_MIN : n > -DBL_MIN);
}

// 對比兩個 double 是否相等
inline
bool double_equal(double l, double r)
{
    return (l >= r ? l - r < DBL_EPSILON : r - l < DBL_EPSILON);
}

// 列印函式的結果
#define TEST_BOOL_FUNC(func) _tprintf(_T("%s: %s\n"), _TSTRINGIZE(func), func ? _T("TRUE") : _T("FALSE"))

// 使用者程式碼
// 對比 double 是否為 0 時,double_equal0() 更精確
// 對比兩個 double 是否相等時,最好用 double_equal()

TEST_BOOL_FUNC(double_equal0(0));                       // TRUE
TEST_BOOL_FUNC(double_equal0(DBL_EPSILON));             // FALSE
TEST_BOOL_FUNC(double_equal0(-DBL_EPSILON));            // FALSE
TEST_BOOL_FUNC(double_equal0(DBL_MIN));                 // FALSE
TEST_BOOL_FUNC(double_equal0(-DBL_MIN));                // FALSE

TEST_BOOL_FUNC(double_equal(0, 0));                     // TRUE
TEST_BOOL_FUNC(double_equal(DBL_EPSILON, 0));           // FALSE
TEST_BOOL_FUNC(double_equal(DBL_MIN, 0));               // TRUE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_EPSILON));   // FALSE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_EPSILON));   // FALSE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 + DBL_MIN));       // TRUE
TEST_BOOL_FUNC(double_equal(1.0, 1.0 - DBL_MIN));       // TRUE

math.h 數學常量^

數學計算常用的浮點數常量,如 M_PI (pi), M_E (e), M_SQRT2 (sqrt(2)) 等。這些數學常量不是標準 C/C++ 的一部分,而是 Microsoft 的擴充套件,使用前需要定義 _USE_MATH_DEFINES:

  1. #define _USE_MATH_DEFINES
  2. #include <math.h>
#define _USE_MATH_DEFINES
#include <math.h>

EOF 常量^

EOF (end-of-file) 常量,定義為 (-1),有寬字元版 WEOF ((wint_t)(0xFFFF)),EOF 和 WEOF 在 stdio.h 中定義,還有 _TCHAR 版 _TEOF,在 tchar.h 中定義。EOF 在流、I/O 操作中表示到達流、檔案末尾(EOF 條件),也用來表示發生錯誤情況

例子:標準輸入的 EOF^

  1. // 設定 locale
  2. // 定義寬字元流與控制檯 I/O 字元之間的轉換字符集編碼為系統 ANSI 字符集
  3. // 這樣在中文 Windows 上可輸入、顯示中文字元
  4. _tsetlocale(LC_ALL, _T("")); 
  5. // 要用儲存空間 >= _gettchar() 返回值型別的變數儲存其返回值
  6. // 而不要用 char ch = _getchar(),那樣會截斷其返回值型別
  7. int ch; 
  8. while ((ch = _gettchar()) != _TEOF) 
  9.     _tprintf(_T("[%c]"), (_TCHAR)ch); 
  10. _tprintf(_T("\nread stdin: %s\n"), (feof(stdin) ? _T("EOF") : _T("Error"))); 
// 設定 locale
// 定義寬字元流與控制檯 I/O 字元之間的轉換字符集編碼為系統 ANSI 字符集
// 這樣在中文 Windows 上可輸入、顯示中文字元
_tsetlocale(LC_ALL, _T(""));

// 要用儲存空間 >= _gettchar() 返回值型別的變數儲存其返回值
// 而不要用 char ch = _getchar(),那樣會截斷其返回值型別
int ch;
while ((ch = _gettchar()) != _TEOF)
    _tprintf(_T("[%c]"), (_TCHAR)ch);

_tprintf(_T("\nread stdin: %s\n"), (feof(stdin) ? _T("EOF") : _T("Error")));

測試輸出,用 Ctrl + Z 產生 EOF 訊號:

abc漢字
[a][b][/c][漢][字][
]^Z

read stdin: EOF

errno.h 錯誤程式碼^

在 errno.h 中定義,是測試全域性量 errno 的值,errno 在 VC 中實現為執行緒安全的函式,而非全域性變數。錯誤程式碼以 E 打頭如 EINVAL:不合法的引數錯誤

locale 類別^

locale 類別 (Categories),在 locale.h 中定義,如 LC_ALL、LC_CTYPE

_MAX_PATH 等檔名與路徑長度限制^

包括全路徑與各部分路徑的限制,即 FILENAME_MAX、_MAX_PATH、_MAX_DRIVE、_MAX_EXT、_MAX_FNAME、_MAX_DIR,在 stdlib.h 中定義。最大全路徑長度限制在 260,和 Windows 的 MAX_PATH 相同,這是為了相容 Windows 98 FAT32 檔案系統。CRT 支援 32767 長度的檔名,方法和 Windows API 相同,即使用 "\\?\" 路徑字首,並呼叫 Unicode 寬字元版的 CRT 函式

RAND_MAX 隨機數最大值^

在 stdlib.h 中定義為 32767,rand() 函式會產生 0 ~ RAND_MAX 之間的偽隨機 int 值

例子:用 RAND_MAX 產生某個範圍內的隨機數^

  1. template<bool seed, typename Type> 
  2. inline
  3. Type get_rand(Type min, Type max) 
  4.     _ASSERT(max >= min); 
  5.     if (seed)       // Release 方式編譯時,這個判斷語句會被優化掉
  6.         srand((unsigned int) time(NULL)); 
  7.     return (Type) (((double) rand() / (double) RAND_MAX) * (max - min) + min); 
  8. template<typename Type> 
  9. inline
  10. Type get_rand_seed(Type min, Type max) 
  11.     return get_rand<true>(min, max); 
  12. template<typename Type> 
  13. inline
  14. Type get_rand_noseed(Type min, Type max) 
  15.     return get_rand<false>(min, max); 
  16. // 使用者程式碼
  17. #define RANGE_MIN   10
  18. #define RANGE_MAX   100
  19. int randnum; 
  20. randnum = get_rand_seed(RANGE_MIN, RANGE_MAX); 
  21. randnum = get_rand_noseed(RANGE_MIN, RANGE_MAX); 
template<bool seed, typename Type>
inline
Type get_rand(Type min, Type max)
{
    _ASSERT(max >= min);

    if (seed)       // Release 方式編譯時,這個判斷語句會被優化掉
        srand((unsigned int) time(NULL));

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

template<typename Type>
inline
Type get_rand_seed(Type min, Type max)
{
    return get_rand<true>(min, max);
}

template<typename Type>
inline
Type get_rand_noseed(Type min, Type max)
{
    return get_rand<false>(min, max);
}

// 使用者程式碼
#define RANGE_MIN   10
#define RANGE_MAX   100

int randnum;
randnum = get_rand_seed(RANGE_MIN, RANGE_MAX);
randnum = get_rand_noseed(RANGE_MIN, RANGE_MAX);

va_arg/va_start/va_end 訪問變長函式引數^

用於訪問類似 printf(const char* format, ...) 等變長函式引數的輔助巨集,在 stdarg.h 中宣告,參考 MSDN: va_arg, va_end, va_start

巨集實現的 CRT 函式^

在 VC CRT 中有些函式以巨集和函式兩種方式實現,如 getchar(),並優先使用巨集版本,

強制使用函式版的方法:

(1). 呼叫時給函式名加括號,如 (getchar)()
(2). 呼叫前,取消巨集版本的定義,如 #undef getchar

Microsoft 預定義巨集^

VC C/C++ 和 Microsoft 預定義巨集參考 MSDN: Predefined Macros

這些巨集可以分類如下:

平臺與系統類^

_M_IX86: IA32/x86 平臺
_M_IA64: IA64/IPF (Itanium Processor Family) 64bit 平臺
_M_X64: x64/x86-64/AMD64 平臺
WIN32, _WIN32: Win32 和 Win64 程式開發都會定義
_WIN64: Win64 程式開發
_CONSOLE: 控制檯 Windows 程式開發,連結 Console 子系統:/SUBSYSTEM:CONSOLE
_WINDOWS: 非控制檯 Windows 程式開發,連結 Windows 子系統:/SUBSYSTEM:WINDOWS

版本號類^

通常定義為數字,配合 #if (XXX >= 1000) 使用,啟動、禁用特定部分的程式碼、特性

_MSC_VER: VC 編譯器 cl 版本號。VC 2003 編譯器版本號 13.10 (_MSC_VER = 1310),VC 2005 編譯器版本號 14.00 (_MSC_VER = 1400)。用 cl /? 檢視編譯器版本號
_MFC_VER: MFC 版本號
_ATL_VER: ATL 版本號
__CLR_VER: CLR 版本號
WINVER: 目標 Windows 版本號
_WIN32_WINNT: 目標 Windows NT 版本號
_WIN32_WINDOWS: 目標 Windows 9x 版本號
_WIN32_IE: 目標 IE 版本號

工程配置管理類^

_DEBUG, NDEBUG: Debug/Release 編譯方式
UNICODE, _UNICODE, _MBCS: ANSI/UNICODE/MBCS 字符集支援
_AFXDLL: 動態連結 MFC (DLL)
_ATL_STATIC_REGISTRY, _ATL_DLL: 靜態/動態連結 ATL
_DLL: 動態連結 CRT (DLL),對應 /MD、/MDd 編譯選項
_MT: CRT 多執行緒支援,目前 4 種 CRT 連結方式 /MD、/MDd、/MT、/MTd 都支援多執行緒(VC 2005 已沒有單執行緒版 CRT),加上建立 DLL 模組的 /LD、/LDd,都定義 _MT
_MANAGED: 以 /clr、/clr:pure、/clr:safe 託管方式編譯時,定義為 1
__cplusplus_cli: 以 /clr、/clr:pure、/clr:safe 方式編譯時定義,VC 2005 中定義為 200406L

上面 1、2、3 類巨集通常和條件編譯預處理指令 #if/#ifdef/#ifndef 配合使用

輔助類^

__VA_ARGS__: 在函式式巨集中,代表變長部分引數 (...),參考 MSDN: Variadic Macros

__COUNTER__: include 展開編譯單元后,編譯時第一次遇到 __COUNTER__ 替換為 0,以後在這個編譯每遇到一次 __COUNTER__ 自增一。不同的編譯單元之間 __COUNTER__ 不互相積累疊加,均從 0 開始計數,但預編譯頭 .pch 檔案會記錄 __COUNTER__ 的歷史值,則每個編譯單元均從歷史值 + 1 開始計數。__COUNTER__ 支援巨集的巢狀展開

__FUNCTION__, __FUNCDNAME__, __FUNCSIG__: 表示所在函式的函式名的 char 字串。例如,對於 void test_funcname_macro() 函式原型,它們的值如下:

(1). __FUNCTION__ = test_funcname_macro: 函式的原始名/非修飾名 (undecorated)
(2). __FUNCDNAME__ = [email protected]@YAXXZ: 函式的修飾名 (decorated),可用工具 undname "decorated_name" 得出函式原型和呼叫規範,即 __FUNCSIG__ 所表示的
(3). __FUNCSIG__ = void __cdecl test_funcname_macro(void): 函式的 signature 名,即呼叫約定、返回值型別、引數型別

例子:用 __VA_ARGS__ 列印跟蹤函式呼叫^

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

  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. // 針對引數為 void 的函式
  6. // NOTE: 函式 func() 使用 func(__VA_ARGS__) 展開時,會影響前面的變長引數函式 _tprintf(),
  7. // 導致執行時緩衝區訪問違例(Debug 方式產生保護中斷),所以不能用前兩版帶 func(__VA_ARGS__) 的 CALL_TRACE
  8. #define CALL_TRACE_VOIDPARM(func, ret)  { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(); }
  9. // 針對返回值、引數均為 void 的函式
  10. #define CALL_TRACE_VOID_VOIDPARM(func)  { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(); }
  11. // 使用者程式碼
  12. // Unicode 方式編譯時,輸出 call: CreateFileW,並將返回值傳給 hFile
  13. CALL_TRACE_RET(CreateFile, hFile, _T("bbb"), 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); 
// 針對引數不為 void,且需要儲存返回值的函式
#define CALL_TRACE(func, ret, ...)      { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(__VA_ARGS__); }
// 針對返回值為 void 或不關心返回值的函式
#define CALL_TRACE_VOID(func, ...)      { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(__VA_ARGS__); }

// 針對引數為 void 的函式
// NOTE: 函式 func() 使用 func(__VA_ARGS__) 展開時,會影響前面的變長引數函式 _tprintf(),
// 導致執行時緩衝區訪問違例(Debug 方式產生保護中斷),所以不能用前兩版帶 func(__VA_ARGS__) 的 CALL_TRACE
#define CALL_TRACE_VOIDPARM(func, ret)  { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); ret = func(); }

// 針對返回值、引數均為 void 的函式
#define CALL_TRACE_VOID_VOIDPARM(func)  { _tprintf(_T("call: %s\n"), _TSTRINGIZE(func)); func(); }

// 使用者程式碼
// Unicode 方式編譯時,輸出 call: CreateFileW,並將返回值傳給 hFile
CALL_TRACE_RET(CreateFile, hFile, _T("bbb"), 0, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);

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

  1. namespace std 
  2. typedef std::basic_string<_TCHAR>   _tstring; 
  3. #define FORMAT_STRING(str, buf, sz, ...)    { sprintf_s(buf, sz, __VA_ARGS__); str = buf; }
  4. #define FORMAT_WSTRING(str, buf, sz, ...)   { swprintf_s(buf, sz, __VA_ARGS__); str = buf; }
  5. #define FORMAT_TSTRING(str, buf, sz, ...)   { _stprintf_s(buf, sz, __VA_ARGS__); str = buf; }
  6. // 使用者程式碼
  7. _TCHAR buf[512]; 
  8. _tstring str; 
  9. FORMAT_TSTRING(str, buf, _countof(buf), _T("%s is: %f"), _T("Pi"), M_PI); 
namespace std
{
typedef std::basic_string<_TCHAR>   _tstring;
}

#define FORMAT_STRING(str, buf, sz, ...)    { sprintf_s(buf, sz, __VA_ARGS__); str = buf; }
#define FORMAT_WSTRING(str, buf, sz, ...)   { swprintf_s(buf, sz, __VA_ARGS__); str = buf; }
#define FORMAT_TSTRING(str, buf, sz, ...)   { _stprintf_s(buf, sz, __VA_ARGS__); str = buf; }

// 使用者程式碼
_TCHAR buf[512];
_tstring str;
FORMAT_TSTRING(str, buf, _countof(buf), _T("%s is: %f"), _T("Pi"), M_PI);

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

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

  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. enum MyMask 
  7.     MAKE_MASK0(MASK_0), //  2^0:    1
  8.     MAKE_MASK(MASK_1),  //  2^1:    2 << 0
  9.     MAKE_MASK(MASK_2),  //  2^2:    2 << 1
  10.     MAKE_MASK(MASK_3),  //  2^3:    2 << 2
  11.     MAKE_MASK(MASK_4)   //  2^4:    2 << 3
  12.     // 最大 MASK = MASK_31  2^31:   2 << 30
  13. }; 
// 保證 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__ 列印跟蹤函式呼叫^

  1. #define BEGIN_FUNC  _tprintf(_T("%s BEGIN\n"), _T(__FUNCTION__));
  2. #define END_FUNC    _tprintf(_T("%s END\n"), _T(__FUNCTION__));
  3. // 使用者程式碼
  4. void test_funcname_macro() 
  5.     BEGIN_FUNC 
  6.     // 函式的功能程式碼
  7.     END_FUNC 
#define BEGIN_FUNC  _tprintf(_T("%s BEGIN\n"), _T(__FUNCTION__));
#define END_FUNC    _tprintf(_T("%s END\n"), _T(__FUNCTION__));

// 使用者程式碼
void test_funcname_macro()
{
    BEGIN_FUNC
    // 函式的功能程式碼
    END_FUNC
}

Windows API 中的註釋性巨集^

註釋性巨集,即是否使用它們不影響編譯結果,通常定義為空

目的:

(1). 在原始碼中起到註解 (annotation) 和標註 (marker) 作用,便於閱讀和理解程式碼功能
(2). 指導 lint 等靜態程式碼檢查工具檢查程式碼缺陷
(3). 指導文件自動生成工具掃描原始檔,生成類、函式/API 參考文件

如 WinDef.h 中定義的 IN、OUT、OPTIONAL 用來說明函式引數或型別成員的傳入、傳出、可選性質

sal.h 中有更完整和複雜的註釋性巨集,SAL (Source code Annotation Language) 參考 sal.h 原始檔和 MSDN: SAL Annotations

Windows API 和 CRT 都用 SAL 註釋,幾個常用的如下:

__in: 傳入引數
__out: 傳出引數
__inout: 傳入且傳出引數
__in_opt, __out_opt, __inout_opt: 可選引數,可以為 NULL

如 CreateFileW() 的宣告:

  1. // WinBase.h
  2. WINBASEAPI 
  3. __out 
  4. HANDLE
  5. WINAPI 
  6. CreateFileW( 
  7.     __in     LPCWSTR lpFileName, 
  8.     __in     DWORD dwDesiredAccess, 
  9.     __in     DWORD dwShareMode, 
  10.     __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes, 
  11.     __in     DWORD dwCreationDisposition, 
  12.     __in     DWORD dwFlagsAndAttributes, 
  13.     __in_opt HANDLE hTemplateFile 
  14.     ); 
// WinBase.h

WINBASEAPI
__out
HANDLE
WINAPI
CreateFileW(
    __in     LPCWSTR lpFileName,
    __in     DWORD dwDesiredAccess,
    __in     DWORD dwShareMode,
    __in_opt LPSECURITY_ATTRIBUTES lpSecurityAttributes,
    __in     DWORD dwCreationDisposition,
    __in     DWORD dwFlagsAndAttributes,
    __in_opt HANDLE hTemplateFile
    );

Windows API 中的常用巨集^

Windows API 包含大量旗標、掩碼、狀態碼、錯誤碼等常量式巨集

函式式巨集最常用的有:

型別輔助類^

BYTE    HIBYTE(WORD wValue)
BYTE    LOBYTE(WORD wValue)

WORD    HIWORD(DWORD dwValue)
WORD    LOWORD(DWORD dwValue)
WORD    MAKEWORD(BYTE bLow, BYTE bHigh)

LONG    MAKELONG(WORD wLow, WORD wHigh)
LRESULT MAKELRESULT(WORD wLow, WORD wHigh)
LPARAM  MAKELPARAM(WORD wLow, WORD wHigh)
WPARAM  MAKEWPARAM(WORD wLow, WORD wHigh)

GDI 類^

DWORD       MAKEROP4(DWORD fore, DWORD back): used in MaskBlt()

LONG        DIBINDEX(WORD wColorTableIndex)
COLORREF    PALETTEINDEX(WORD wPaletteIndex)

COLORREF    PALETTERGB(BYTE bRed, BYTE bGreen, BYTE bBlue)
COLORREF    RGB(BYTE byRed, BYTE byGreen, BYTE byBlue)

BYTE        GetBValue(DWORD rgb)
BYTE        GetGValue(DWORD rgb)
BYTE        GetRValue(DWORD rgb)

POINTS      MAKEPOINTS(DWORD dwValue)

另外,BITMAP_WIDTHBYTES(bits) 不在 Windows API 中,但比較常用於點陣圖:

  1. // 輸入:點陣圖影象中一行的邏輯位數 = 點陣圖畫素寬 x 每畫素位數
  2. // 輸出:點陣圖影象中一行佔用的位元組數,按 4 Bytes 對齊
  3. #define BITMAP_WIDTHBYTES(bits)     (((bits) + 31) >> 5 << 2)
// 輸入:點陣圖影象中一行的邏輯位數 = 點陣圖畫素寬 x 每畫素位數
// 輸出:點陣圖影象中一行佔用的位元組數,按 4 Bytes 對齊
#define BITMAP_WIDTHBYTES(bits)     (((bits) + 31) >> 5 << 2)

錯誤處理類^

標記沒有使用的引數、變數輔助巨集^

UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_PARAMETER(P)
DBG_UNREFERENCED_LOCAL_VARIABLE(V)

讓沒有使用的引數、變數不產生編譯警告,並且關閉 lint 缺陷檢查報告

錯誤碼、狀態碼^

Windows 有三大錯誤碼、狀態碼空間:

(1). Win32 狀態碼:GetLastError() 所返回,DWORD 型別,WinError.h 中定義
(2). COM 狀態碼:COM 函式用,HRESULT 型別,WinError.h 中定義
(3). 核心狀態碼:核心函式和低階 API 用,NTSTATUS 型別,ntstatus.h 中定義

狀態碼有關的巨集:

MAKE_HRESULT(sev, fac, code): 將 severity、facility、code 合併為 HRESULT
HRESULT_CODE(hr): 取得 HRESULT 的 code 部分
HRESULT_FACILITY(hr): 取得 HRESULT 的 facility 部分
HRESULT_SEVERITY(hr): 取得 HRESULT 的 severity 位

HRESULT_FROM_NT(nt_stat): 從 NTSTATUS 變換到 HRESULT
HRESULT_FROM_WIN32(win_err): 從 Win32 狀態碼變換到 HRESULT

SUCCEEDED(hr): HRESULT 是否表示成功
FAILED(hr): HRESULT 是否表示失敗
IS_ERROR(hr): HRESULT 是否表示一個錯誤

Win32 狀態碼沒有類似 MAKE_HRESULT 的巨集,自定義 Win32 狀態碼時可以用 mc (Message Compiler) 工具處理 .mc 指令碼,自動生成含自定義 Win32 狀態碼的標頭檔案,同時生成用於 FormatMessage() 的狀態碼文字描述,參考 MSDN:Message Compiler

也可以自定義用於 Win32 狀態碼的 MAKE_WINERR():

  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. // Win32 狀態碼的各部分起始位、位掩碼和位長度
  29. #define WINERR_SEVERITY_BIT_LOW         30
  30. #define WINERR_SEVERITY_MASK            0xC0000000
  31. #define WINERR_SEVERITY_BIT_LEN         2
  32. #define WINERR_SEVERITY_VALUE(val)      (((val) << WINERR_SEVERITY_BIT_LOW) & WINERR_SEVERITY_MASK)
  33. #define WINERR_CUSTOM_DEFINE_BIT_LOW    29
  34. #define WINERR_CUSTOM_DEFINE_MASK       0x20000000
  35. #define WINERR_CUSTOM_DEFINE_BIT_LEN    1
  36. #define WINERR_CUSTOM_DEFINE_FLAG       (1 << WINERR_CUSTOM_DEFINE_BIT_LOW)
  37. #define WINERR_FACILITY_BIT_LOW         16
  38. #define WINERR_FACILITY_MASK            0x0FFF0000
  39. #define WINERR_FACILITY_BIT_LEN         12
  40. #define WINERR_FACILITY_VALUE(val)      (((val) << WINERR_FACILITY_BIT_LOW) & WINERR_FACILITY_MASK)
  41. #define WINERR_CODE_BIT_LOW             0
  42. #define WINERR_CODE_MASK                0x0000FFFF
  43. #define WINERR_CODE_BIT_LEN             16
  44. #define WINERR_CODE_VALUE(val)          (val) & WINERR_CODE_MASK
  45. // Win32 狀態碼中的嚴重級別 severity
  46. #define WINERR_SEVERITY_SUCCESS         0
  47. #define WINERR_SEVERITY_INFORM          1
  48. #define WINERR_SEVERITY_WARNING         2
  49. #define WINERR_SEVERITY_ERROR           3
  50. #define WINERR_SEVERITY_NOT_CARE        3
  51. // 自定義 Win32 狀態碼的巨集
  52. #define MAKE_WINERR(sev, fac, code)     \
  53.     ((DWORD)(WINERR_SEVERITY_VALUE(sev) | WINERR_CUSTOM_DEFINE_FLAG | WINERR_FACILITY_VALUE(fac) | WINERR_CODE_VALUE(code))) 
//  copy from WinError.h
//
//  Values are 32 bit values layed out as follows:
//
//   3 3 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1
//   1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
//  +---+-+-+-----------------------+-------------------------------+
//  |Sev|C|R|     Facility          |               Code            |
//  +---+-+-+-----------------------+-------------------------------+
//
//  where
//
//      Sev - is the severity code
//
//          00 - Success
//          01 - Informational
//          10 - Warning
//          11 - Error
//
//      C - is the Customer code flag
//
//      R - is a reserved bit
//
//      Facility - is the facility code
//
//      Code - is the facility's status code
//

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

#define WINERR_SEVERITY_BIT_LOW         30
#define WINERR_SEVERITY_MASK            0xC0000000
#define WINERR_SEVERITY_BIT_LEN         2
#define WINERR_SEVERITY_VALUE(val)      (((val) << WINERR_SEVERITY_BIT_LOW) & WINERR_SEVERITY_MASK)

#define WINERR_CUSTOM_DEFINE_BIT_LOW    29
#define WINERR_CUSTOM_DEFINE_MASK       0x20000000
#define WINERR_CUSTOM_DEFINE_BIT_LEN    1
#define WINERR_CUSTOM_DEFINE_FLAG       (1 << WINERR_CUSTOM_DEFINE_BIT_LOW)

#define WINERR_FACILITY