首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

為什麼我在宏定義中拒絕使用 do{}while(0)?

     編輯:關於C語言

緣起:一場關於宏寫法的爭論

幾天前我發表了一篇關於 C 語言可變參數處理的博文,本意是分享用函數型宏處理變參的技巧,沒想到卻因為宏的封裝方式引發了激烈討論。許多資深工程師關注的焦點不是變參處理本身,而是為什麼我沒用業界常見的 do{}while(0) 來包裹宏,而是直接用大括號 {}。

這讓我想起《0bug-C/C++商用工程之道》出版時的類似經歷:部分讀者不是為了學習書中的核心思想,而是專門找錯別字、找技術細節的「錯誤」來彰顯自己水平。這種「買櫝還珠」的閱讀方式,實際上阻礙了真正的技術成長。

我的代碼示例與替代方案

在原文中,我定義了這樣的宏:

#define TONY_FORMAT(nPrintLength, szBuf, nBufferSize, szFormat) \
{ \
    va_list pArgList; \
    va_start(pArgList, szFormat); \
    nPrintLength += Linux_Win_vsnprintf(szBuf + nPrintLength, \
        nBufferSize - nPrintLength, szFormat, pArgList); \
    va_end(pArgList); \
    if (nPrintLength > (nBufferSize - 1)) nPrintLength = nBufferSize - 1; \
    *(szBuf + nPrintLength) = '\0'; \
}

因為沒用 do{}while(0),在 if-else 中調用時必須顯式加大括號:

if (!bWithTimestamp) {
    TONY_FORMAT(nLength, szBuf, TONY_LINE_MAX, szFormat);
} else {
    TONY_FORMAT_WITH_TIMESTAMP(nLength, szBuf, TONY_LINE_MAX, szFormat);
}

當然,也可以通過去掉調用後的分號來避免加大括號,但這違背了常規編碼習慣。

核心觀點:拒絕「掛羊頭賣狗肉」的語法濫用

我堅持不使用 do{}while(0) 的根本原因在於語義清晰度與團隊規範:

1. do-while 的設計初衷是循環,而非宏包裹

do{}while(0) 字面語義是「循環執行零次」,但實際卻執行一次。這種「字面與行為相反」的特性極易誤導初學者:

int i = 0;
do {
    i++;
} while (0);  // i 變成 1,而非 0

更令人困惑的是:while(0) 執行一次,while(1) 卻是死循環。這種反直覺的語義違背了「代碼即文檔」的原則。

2. 這是典型的非標準濫用

C 標準從未規定 do-while 用於宏封裝。將循環語句強行用作宏包裹符,屬於「掛羊頭賣狗肉」,增加了代碼的認知負荷。

3. 規範化開發的取捨哲學

我的選擇基於《0bug》書中提倡的語言裁剪使用原則:在多種解法中,選擇「最簡單、最不易出錯、最易閱讀」的一種並形成團隊標準。雖然用大括號要求調用處加 {},但這符合直覺,且編譯器會強制檢查結構匹配,反而減少了隱性錯誤。

4. 實戰驗證:15 年零事故

這套規範在我的團隊執行超過 15 年,2008 年帶領團隊開發商用服務器集群(十幾萬行 C/C++ 代碼),僅 51 個 Bug,核心層僅 7 個。規範化看似限制了靈活性,實則保障了交付質量與團隊效率。

給讀者的建議

  • 閱讀技術文章時,請先理解作者的核心意圖,再討論細節實現。
  • 關注「如何寫出不易出錯、易維護的代碼」,而非炫技語法技巧。
  • 團隊開發中,統一規範遠比個人偏好重要。

我寫代碼不是為了炫耀技巧,而是為了簡單、直接地交付無 Bug 的產品,讓團隊賺到錢。這種工程化思維,才是我希望傳遞的核心價值。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved