php底層原理之函式

許錚的技術成長之路發表於2019-04-14

對於PHPer而言,我們通常會把常用的功能封裝成一個函式來進行復用,以提升開發效率。但是php到底是如何找到對應的函式的呢?今天,我們來一起尋找答案~

函式分類

首先,我們先回顧一下php的函式分類,php函式包含使用者自定義函式、內部函式、匿名函式等多種型別。而對於使用者自定義函式和內部函式,他們分別存在對應自己的資料結構,但是Zend引擎為了適配兩種函式型別,所以定義了一種新的資料結構:zend_function聯合體

資料結構

我們還是先看下zend_function聯合體,瞭解下為什麼針對使用者自定義函式和內部函式要做適配

typedef union _zend_function {
    zend_uchar type;    //函式型別,用來標記是使用者自定義函式還是內部函式
    struct {
        zend_uchar type;  /* never used */
        char *function_name;    //函式名稱
        zend_class_entry *scope; //函式所在的類作用域,用來標記作為成員方法時所屬的類
        zend_uint fn_flags;     // 標記其作為類的成員方法時的訪問型別,是public、protected還是private
        union _zend_function *prototype;  //函式原型,標記內部函式或者使用者自定義函式所屬的zend_function
        zend_uint num_args;     //函式的引數數量
        zend_uint required_num_args; //必傳的引數數量
        zend_arg_info *arg_info;  //引數資訊指標
        zend_bool pass_rest_by_reference;
        unsigned char return_reference;  //返回值 
    } common;
    zend_op_array op_array;   //使用者自定義函式結構體
    zend_internal_function internal_function; //內部函式結構體
} zend_function;
struct _zend_op_array {
    /* Common elements (共有元素)*/
    zend_uchar type;
    char *function_name;        
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */
 
    zend_bool done_pass_two;
    ....//  其它欄位
}
typedef struct _zend_internal_function {
    /* Common elements (共有元素)*/
    zend_uchar type;
    char * function_name;
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */
 
    void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
    struct _zend_module_entry *module;
} zend_internal_function;
複製程式碼

從上面介紹的內容,我們可以發現,不管是使用者自定義函式還是內部函式,在底層儲存時都會存在共有欄位typecommon,所以他們以聯合體的方式共享記憶體,可以節省記憶體空間和快速獲取函式的基本資訊,並且如果有需要,可以在一些結構間完美的進行強制型別轉換。即zend_function可以與zend_op_array互換,zend_function可以與zend_internal_function互換

函式註冊

聊完了使用者自定義函式和內部函式的資料結構儲存,我們再來看下全域性函式列表

全域性函式列表,可以理解成函式登錄檔,其內部實現是一個雜湊表。使用者自定義函式和內部函式編譯完成後會將函式名註冊到全域性函式列表中。也就是此時會判斷是否全域性函式列表中存在同名函式

那麼使用者自定義函式和內部函式在儲存到全域性函式列表時有什麼不同呢?

使用者自定義函式: 我們寫的php函式在編譯階段會經過詞法分析->語法分析->生成中間程式碼opcode->執行中間程式碼的過程,執行中間程式碼時,會將函式名註冊到全域性函式列表

內部函式: php啟動時,會載入所有擴充套件模組,併為擴充套件模組中每一個函式建立一個zend_internal_function結構,並將這個函式名註冊到全域性函式列表

函式呼叫

接下來,我們再來看下呼叫函式時,是如何執行的呢?

函式呼叫時,首先會根據函式名去全域性函式列表內查詢是否存在該函式,如果不存在,則會直接報出“Call to undefined function xxx()"的錯誤資訊;如果存在,則獲取該函式指標對應的函式結構中的type欄位,判斷其函式型別,如果函式型別是自定義函式,則呼叫zend_execute來執行函式的zend_op_array內容,而如果函式型別是內部函式,則直接呼叫zend_internal_function的handle指標指向的擴充套件模組的C函式

總結

到這裡,大家應該可以找到開頭我們提出的問題的答案了。其實就是通過函式註冊到全域性函式列表,然後函式呼叫時,再從全域性函式列表中查詢對應函式進行執行來實現的;只不過,對於使用者自定義函式和內部函式而言,其實現方式不同,當然這也就意味著執行效率的不同(當然php內部函式執行效率更高了,因為它沒有執行時的編譯階段,相當於直接執行c語言,所以能用php內部函式的儘量使用內部函式)

今天我們就聊到這裡了,歡迎大家的手動點贊~

相關文章