錯誤碼 ErrID 與錯誤訊息 errmsg 的一對一映射,需滿足:
get_err_msg(id))。enum { ERR_1, ERR_2, ERR_3, ERR_4 } ErrID;
const char *errmsg[] = {
"Error 1", // ERR_1
"Error 2", // ERR_2
"Error 3", // ERR_3
"Error 4" // ERR_4
};
缺點:人工對齊註解,調整順序極易錯誤,無法編譯期檢查對應關係。
#define ERR_MSG(id, msg) const char *err_msg_##id = msg;
#define GET_MSG(id) err_msg_##id
ERR_MSG(ERR_1, "Error 1")
ERR_MSG(ERR_2, "Error 2")
ERR_MSG(ERR_3, "Error 3")
ERR_MSG(ERR_4, "Error 4")
// 使用
printf("%s", GET_MSG(ERR_1));
優點:無陣列開銷,編譯期檢查 id 存在性。
缺點:ErrID 必須為編譯期常數,無法 for (id=0; idget_err_msg(runtime_id)。
err.def/* err.def - 唯一資料來源 */
ERR_MSG(ERR_1, "Error 1")
ERR_MSG(ERR_2, "Error 2")
ERR_MSG(ERR_3, "Error 3")
ERR_MSG(ERR_4, "Error 4")
/* 1. 產生 enum */
#undef ERR_MSG
#define ERR_MSG(id, msg) id,
enum {
#include "err.def"
ERR_MAX // 自動獲得總數
} ErrID;
/* 2. 產生字串陣列 */
#undef ERR_MSG
#define ERR_MSG(id, msg) msg,
const char *errmsg[] = {
#include "err.def"
"" // 尾部空字串避免尾隨逗號警告
};
/* 3. 產生查詢函式 */
#undef ERR_MSG
#define ERR_MSG(id, msg) case id: return msg;
const char *get_err_msg(ErrID id) {
switch (id) {
#include "err.def"
}
return NULL;
}
| 需求 | 模式一 | 模式二 | 模式三 (X-Macro) |
|---|---|---|---|
| 零開銷 | ✓ | ✓ | ✓ |
| 單一資料源 | ✗ | ✓ | ✓ |
| 編譯期檢查 | ✗ | ✓ | ✓ |
| 支援遍歷 | ✓ | ✗ | ✓ (errmsg[]) |
| 支援動態查詢 | ✓ | ✗ | ✓ (switch) |
| 自動計數 | ✗ | ✗ | ✓ (ERR_MAX) |
/* err.def */
ERR_MSG(ERR_1, "Error 1", SEV_FATAL, MOD_NET, 500)
ERR_MSG(ERR_2, "Error 2", SEV_WARN, MOD_UI, 400)
/* 主檔 */
#define ERR_MSG(id, msg, sev, mod, http) {id, msg, sev, mod, http},
const ErrorEntry err_table[] = {
#include "err.def"
};
#undef ERR_MSG
#define ERR_MSG(id, msg) #id,
const char *err_names[] = { #include "err.def" };
// err_names[ERR_1] == "ERR_1"
利用 Python 腳本讀取 err.def(或 CSV/JSON),產生各語言對應檔案,實現跨語言錯誤碼一致性。
"" 或 ERR_MAX 兼容兩者。ERR_MSG(ERR_1, ("Error, detail")),或改用單一結構體引數。#include "err.def" 需在編譯器搜尋路徑中,建議放在專案 include/ 目錄並加 -I。#include 前務必 #undef ERR_MSG 再重新定義,避免污染。constexpr// C++17 constexpr 映射表
constexpr std::array err_table = {{
{ERR_1, "Error 1", SEV_FATAL},
{ERR_2, "Error 2", SEV_WARN},
// ...
}};
// 編譯期查詢
consteval const char* get_msg(ErrID id) {
for (auto &e : err_table) if (e.id == id) return e.msg;
return nullptr;
}
現代 C/C++ 可直接用語言級常數表達式替代 X-Macro,但 X-Macro 仍是純 C89/C99 專案、跨語言代碼生成、預處理器元編程的最佳選擇。
靜態映射表演進路徑:平行陣列 → 巨集分離變數 → X-Macro 檔案包含。X-Macro 以「單一資料來源、多重巨集展開」完美解決維護性與效能的矛盾,是 C 語言工程化的經典模式。新專案若支援 C++17/C23,優先考慮 constexpr;舊專案或需跨語言同步時,X-Macro 仍是首選。