函數是任何程式語言的基本構建單元。PHP 函數具備哪些特性?調用機制如何實現?效能表現如何?有哪些使用建議?本文從底層原理出發,結合實測數據,系統闡述這些核心問題。
橫向劃分為兩大類:用戶函數(user function,含自定義函數與方法)與 內建函數(internal function,如 sprintf、array_push 等)。用戶可通過編寫擴展新增內建函數。用戶函數再細分為:function(普通函數)與 method(類方法)。本系列將對這三類分別深入分析。
PHP 採用典型動態語言執行流程:
詞法分析 → 語法分析 → 編譯為 opcodes → ZEND 虛擬機順序執行指令。
PHP 本身由 C 實現,底層最終調用 C 函數。函數調用同樣被翻譯為 opcodes,每次調用執行一條或多條指令。

ZEND 使用 zend_function 聯合體描述所有函數:
typedef union _zend_function {
zend_uchar type; // 函數類型:用戶/內建/重載
struct {
zend_uchar type;
char *function_name;
zend_class_entry *scope;
zend_uint fn_flags;
// ... 參數、返回值引用等資訊
} common;
zend_op_array op_array; // 用戶函數對應的 opcode 陣列
zend_internal_function internal_function; // 內建函數入口
} zend_function;
ZEND 維護一個全域 function_table(大型 Hash Table),函數調用時按名稱查找對應的 zend_function,再根據 type 決定執行路徑。不同類型函數執行原理截然不同。
內建函數本質為 C 函數,編譯後展開為 zif_xxxx 形式(如 zif_sprintf)。ZEND 執行時僅做簡單轉發。提供豐富 API 供參數解析(zend_parse_parameters)、陣列操作、記憶體管理等。參數傳遞採用淺拷貝(引用計數),效率極高。結論:內建函數效能與對應 C 函數幾乎一致,僅多一次轉發調用。
內建函數以 .so 動態載入,開發者可編寫擴展新增功能。
用戶函數對應一組 opcodes,儲存於 zend_function.op_array。調用即執行這組指令。
ZEND 為每個用戶函數分配「活動符號表」(active_sym_table),記錄局部變量狀態。所有符號表以棧形式維護:函數調用時分配新表入棧,返回時出棧並清理,從而實現狀態隔離與遞歸支援。
優化策略: 預分配長度為 32 的靜態陣列模擬棧,避免頻繁記憶體分配銷毀。調用深度超過 32 時退回動態分配,效能大幅下降。建議:函數調用層次不超過 32 層。
不同於內建函數用 zend_parse_params,用戶函數參數獲取通過指令完成,每個參數對應一條賦值指令。因需維護符號表、每條指令均為 C 函數調用,用戶函數效能顯著低於內建函數。原則:有內建函數可用時,堅決不手寫同功能用戶函數。