天天看點

五十六、PHP核心探索:函數的内部結構 ☞ 在其内部可以實作各種語句的執行

在PHP中,函數有自己的作用域,同時在其内部可以實作各種語句的執行,最後傳回最終結果值。 在PHP的源碼中可以發現,PHP核心将函數分為以下類型:

#define ZEND_INTERNAL_FUNCTION 1

#define ZEND_USER_FUNCTION 2

#define ZEND_OVERLOADED_FUNCTION 3

#define ZEND_EVAL_CODE 4

#define ZEND_OVERLOADED_FUNCTION_TEMPORARY 5

其中的ZEND_USER_FUNCTION是使用者函數,ZEND_INTERNAL_FUNCTION是内置的函數。也就是說PHP将内置的函數和使用者定義的函數分别儲存。

使用者函數(ZEND_USER_FUNCTION)

使用者自定義函數是非常常用的函數種類,如下面的代碼,定義了一個使用者自定義的函數:

<?php

function nowamagic( $name ){

$return = "Hi! " . $name;

echo $return;

return $return;

}

?>

這個示例中,對自定義函數傳入了一個參數,并将其與Hi! 一起輸出并做為傳回值傳回。 從這個例子可以看出函數的基本特點:運作時聲明、可以傳參數、有值傳回。 當然,有些函數隻是進行一些操作,并不一定顯式的有傳回值,在PHP的實作中,即使沒有顯式的傳回, PHP核心也會“幫你“傳回NULL。

ZE在執行過程中,會将運作時資訊存儲于_zend_execute_data中:

struct _zend_execute_data {

//...省略部分代碼

zend_function_state function_state;

zend_function *fbc; /* Function Being Called */

//...省略部分代碼

};

在程式初始化的過程中,function_state也會進行初始化,function_state由兩個部分組成:

typedef struct _zend_function_state {

zend_function *function;

void **arguments;

} zend_function_state;

**arguments是一個指向函數參數的指針,而函數體本身則存儲于*function中, *function是一個zend_function結構體, 它最終存儲了使用者自定義函數的一切資訊,它的具體結構是這樣的:

typedef union _zend_function {
    zend_uchar type;    /* 如使用者自定義則為 #define ZEND_USER_FUNCTION 2
                            MUST be the first element of this struct! */
 
    struct {
        zend_uchar type;  /* never used */
        char *function_name;    //函數名稱
        zend_class_entry *scope; //函數所在的類作用域
        zend_uint fn_flags;     // 作為方法時的通路類型等,如ZEND_ACC_STATIC等  
        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;  //傳回值 
    } common;
 
    zend_op_array op_array;   //函數中的操作
    zend_internal_function internal_function;  
} zend_function;      

zend_function的結構中的op_array存儲了該函數中所有的操作,當函數被調用時,ZE就會将這個op_array中的opline一條條順次執行, 并将最後的傳回值傳回。 從VLD擴充中檢視的關于函數的資訊可以看出,函數的定義和執行是分開的,一個函數可以作為一個獨立的運作單元而存在。

内部函數(ZEND_INTERNAL_FUNCTION)

ZEND_INTERNAL_FUNCTION函數是由擴充或者Zend/PHP核心提供的,用“C/C++”編寫的,可以直接執行的函數。如下為内部函數的結構:

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;      

最常見的操作是在子產品初始化時,ZE會周遊每個載入的擴充子產品,然後将子產品中function_entry中指明的每一個函數(module->functions), 建立一個zend_internal_function結構, 并将其type設定為ZEND_INTERNAL_FUNCTION,将這個結構填入全局的函數表(HashTable結構); 函數設定及注冊過程見 Zend/zend_API.c檔案中的zend_register_functions函數。這個函數除了處理函數,也處理類的方法,包括那些魔術方法。

内部函數的結構與使用者自定義的函數結構基本類似,有一些不同,

  • 調用方法,handler字段. 如果是ZEND_INTERNAL_FUNCTION, 那麼ZE就調用zend_execute_internal,通過zend_internal_function.handler來執行這個函數。 而使用者自定義的函數需要生成中間代碼,然後通過中間代碼映射到相對就把方法調用。
  • 内置函數在結構中多了一個module字段,表示屬于哪個子產品。不同的擴充其子產品不同。
  • type字段,在使用者自定義的函數中,type字段幾科無用,而内置函數中的type字段作為幾種内部函數的區分。

變量函數

PHP 支援變量函數的概念。這意味着如果一個變量名後有圓括号,PHP 将尋找與變量的值同名的函數,并且将嘗試執行它。 除此之外,這個可以被用于實作回調函數,函數表等。 對比使用變量函數和内部函數的調用:

變量函數$func

$func = 'print_r';

$func('i am print_r function.');

通過VLD來檢視這段代碼編譯後的中間代碼:

function name:  (null)
number of ops:  9
compiled vars:  !0 = $func
line     # *  op                           fetch          ext  return  operands
--------------------------------------------------------------------------------
-
   2     0  >   EXT_STMT
         1      ASSIGN                                                   !0, 'print_r'
   3     2      EXT_STMT
         3      INIT_FCALL_BY_NAME                                       !0
         4      EXT_FCALL_BEGIN
         5      SEND_VAL                                                 'i+am+print_r+function.'
         6      DO_FCALL_BY_NAME                              1
         7      EXT_FCALL_END
         8    > RETURN                                                   1      

内部函數print_r

print_r('i am print_r function.');

通過VLD來檢視這段代碼編譯後的中間代碼:

function name:  (null)
number of ops:  6
compiled vars:  none
line     # *  op                           fetch          ext  return  operands
--------------------------------------------------------------------------------
-
   2     0  >   EXT_STMT
         1      EXT_FCALL_BEGIN
         2      SEND_VAL                                                 'i+am+print_r+function.'
         3      DO_FCALL                                      1          'print_r'
         4      EXT_FCALL_END
         5    > RETURN                                                   1      

對比發現,二者在調用的中間代碼上存在一些差別。變量函數是DO_FCALL_BY_NAME,而内部函數是DO_FCALL。 這在文法解析時就已經決定了, 見Zend/zend_complie.c檔案的zend_do_end_function_call函數中部分代碼:

if (!is_method && !is_dynamic_fcall && function_name->op_type==IS_CONST) {
        opline->opcode = ZEND_DO_FCALL;
        opline->op1 = *function_name;
        ZVAL_LONG(&opline->op2.u.constant, zend_hash_func(Z_STRVAL(function_name->u.constant), Z_STRLEN(function_name->u.constant) + 1));
    } else {
        opline->opcode = ZEND_DO_FCALL_BY_NAME;
        SET_UNUSED(opline->op1);
    }