深刻理解PHP內核(十一)函數-函數的內部結構

原文連接:http://www.orlion.ga/330/php

php的函數包括用戶定義的函數、內部函數(print_r count…)、匿名函數、變量函數($func = 'print_r'; $func(array('a','b'));)c++

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

1、用戶函數(ZEND_USER_FUNCTION)fetch

  函數不必定顯式的有返回值,在PHP的實現中即便沒有顯式的返回,PHP內核也會幫咱們返回NULL。ui

  ZEND在執行過程當中,會將運行時信息存儲於_zend_execute_data中:this

複製代碼

struct _zend_execute_data {    //...省略部分代碼    zend_function_state function_state;
    zend_function *fbc; /* Function Being Called */
    //...省略部分代碼};

複製代碼

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

typedef struct _zend_function_state {
    zend_function *function;    void **arguments;
} zend_function_state;

  *arguments是一個指向函數參數的指針,而函數體本事存儲於*function中,*function是一個zend_function結構體,它最終存儲了用戶自定義函數的一切信息,具體結構以下:prototype

複製代碼

typedef union _zend_function {
    zend_uchar type;    /* 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;     //函數類型,如用戶自定義則爲 #define ZEND_USER_FUNCTION 2  
        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存儲了該函數中的全部操做,當函數被調用時,ZEND就會將這個op_array中的opline一條條順序執行,並將最後的結果返回。函數的定義和執行是分開的,一個函數能夠做爲一個獨立的運行單元存在。指針

2、內部函數(ZEND_INTERNAL_FUNCTION)rest

  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/zene_API.c文件中的zend_register_function函數,這個函數除了處理函數頁也處理類的方法,包括那些魔術方法。

  內部函數的結構與用戶自定義函數結構基本相似,有一些不一樣:

  •   調用方法,handler字段,若是是ZEND_INTERNAL_FUNCTION,那麼ZEND就會調用zend_execute_internal,經過zend_internal_function.handler來執行這個函數。而用戶自定義函數須要生成中間代碼,而後經過中間代碼映射到相對就把方法調用。

  • 內置函數在結構中多了一個module字段,表示屬於哪一個模塊。不一樣的擴展模塊不一樣

  • type字段,在用戶自定義函數中,type字段幾乎無用,而內置函數中的type字段做爲幾種內部函數的區分。

3、變量函數

  若是一個變量名後邊有圓括號,php將尋找與變量的值同名的函數,而且嘗試執行。

  變量函數$func

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

  編譯後中間代碼

複製代碼

function name:  (null)
number of ops:  9compiled 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('i am print_r function.');

  編譯後中間代碼

複製代碼

function name:  (null)
number of ops:  6compiled 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);
    }

複製代碼

  若是不是方法,而且不是動態調用,而且函數名爲字符串變量,則其生成的中間代碼爲ZEND_DO_FCALL。其餘狀況則爲ZEND_DO_FCALL_BY_NAME。另外將變量函數做爲回調函數,其處理過程在Zend/zend_complie.c文件的zend_do_pass_param函數中,最終會體如今中間代碼執行過程當中的ZEND_SEND_VAL_SPEC_CONST_HADNLER等函數中。

 

4、匿名函數

  匿名函數是一類不須要指定表示符,而又能夠被調用的函數或子例程,匿名函數能夠方便的做爲參數傳遞給其餘函數。

相關文章
相關標籤/搜索