物件導向語言的建構函數(Constructor)可在 main 執行前自動完成初始化。C 語言雖為程序導向,但透過編譯器擴展同樣能達到此目的——這對框架設計極為關鍵,能讓使用者免去手動呼叫初始化函數的麻煩。
若在 main 內手動呼叫初始化,框架使用者必須記得調用順序,且難以強制執行。若能像 C++ 那樣靠全域物件建構自動跑初始化碼,框架體驗將大幅提升。
__attribute__((constructor))GCC 提供編譯器屬性,只需在函數宣告前加上:
void __attribute__((constructor)) my_init(void) {
// 初始化碼會在 main 之前自動執行
}
多個建構函數依定義順序執行,支援優先級語法 __attribute__((constructor(101))) 控制順序。
.CRT$XCU 區段MSVC 無類似屬性,須利用 #pragma section 將函數指標放入 CRT 初始化區段 .CRT$XCU:
#pragma section(".CRT$XCU", read)
__declspec(allocate(".CRT$XCU")) void (__cdecl *my_ctor)(void) = my_init;
連結器會依區段名排序(.CRT$XCA ~ .CRT$XCZ),依序呼叫區段內的函數指標,達到相同效果。
為統一兩邊寫法,常見做法定義 __CONSTRCT(name, unique_id) 巨集:
#ifdef _MSC_VER
#define __CONSTRCT(a, b) \
void __cdecl __##a##__##b##__init(void); \
__declspec(allocate(".CRT$XCU")) \
void (__cdecl * __ctor__##a##__##b##__init)(void) = __##a##__##b##__init;
#else
#define __CONSTRCT(a, b) __attribute__((constructor))
#endif
使用時:__CONSTRCT(mymodule, 001) void my_init() { ... } 即可跨平台編譯。
模仿 Google Test,利用建構函數自動註冊測試案例,main 只需呼叫 run_all_tests() 即可發現並執行所有測試,無需手動列舉。完整範例可參考筆者部落格:C 建構函數實現詳解。