泛型通常被視為C++、Java等高級語言的專利,通過模板機制實現代碼復用與類型安全。但C語言作為系統級開發基石,同樣能通過函數指針與預處理器宏兩種方式模擬泛型行為。本文將通過完整可編譯代碼,深度剖析這兩種技法的原理、優缺點及適用場景。
核心思想:定義統一的函數指針類型 typedef int (*FXPrintFun)(void *data),針對不同數據類型編寫具體處理函數(print_int、print_float、print_str等),通用入口 print() 接收函數指針與 void* 數據,實現運行時分發。
typedef int (*FXPrintFun)(void *data);
int print(FXPrintFun _print, void *data) {
return _print(data);
}
static int print_int(void *data) {
printf("%d", (int)data); // 直接轉換,僅因int與指針同為4字節「湊巧」生效
return RET_OK;
}
static int print_int2(void *data) {
printf("%d", *(int*)data); // 規範做法:先轉指針再解引用
return RET_OK;
}
代碼中演示了兩種傳遞 int 的方式:print(print_int, (void*)i) 直接強轉值,print(print_int2, (void*)&i) 傳遞地址。前者看似正常輸出,實則利用了32位系統下 int 與指針同為4字節的「巧合」——若換成 float 直接強轉 void*,GCC 會直接報錯「無法轉換」。鐵律:永遠傳遞指針,在具體函數中解引用,保證類型安全與跨平台兼容。
## 符號連接實現編譯期代碼生成利用預處理器的 Token Pasting (##) 機制,根據類型參數自動生成專屬函數名。定義宏 GENERIC_PRINT(TYPE, FORMAT, SUFFIX),調用時展開為 print_int、print_float、print_str 等獨立函數,調用時無需強轉,類型檢查由編譯器完成。
#define GENERIC_PRINT(TYPE, FORMAT, SUFFIX) \
int print##SUFFIX(TYPE data) { \
printf(FORMAT, data); \
return 0; \
}
GENERIC_PRINT(int, "%d", _int)
GENERIC_PRINT(float, "%f", _float)
GENERIC_PRINT(char *, "%s", _str)
int main() {
print_int(123); // 直接調用,類型安全
print_float(3.14f);
print_str("Hello");
}
Makefile 中 SRC = printtest.c 而非包含 .h,因 gcc -E 預處理階段只需 .c 文件,頭文件內容已通過 #include 引入。若寫成 SRC=printtest.c printtest.h,預處理輸出會異常。直接編譯命令:gcc printtest.h printtest.c -o printtest。
| 維度 | 函數指針 | 宏 ## 生成 |
|---|---|---|
| 類型安全 | 運行時靠程序員保證 | 編譯期強類型檢查 |
| 代碼膨脹 | 無 | 每類型生成一份函數體 |
| 調試難度 | 可直接斷點調試 | 宏展開後行號難對應 |
| 適用場景 | 回調、插件架構、運行時決策 | 輕量級類型適配、性能敏感熱路徑 |
結論: 大型項目推薦函數指針,結構清晰、易維護;小型工具或極致性能場景可用宏生成,但需嚴控代碼規模,防止「宏地獄」。