<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                ### 3.2.1 用戶自定義函數的實現 用戶自定義函數是指我們在PHP腳本通過function定義的函數: ```php function my_func(){ ... } ``` 匯編中函數對應的是一組獨立的匯編指令,然后通過call指令實現函數的調用。前面已經說過PHP編譯的結果是opcode數組,與匯編指令對應。PHP用戶自定義函數的實現就是將函數編譯為獨立的opcode數組,調用時分配獨立的執行棧依次執行opcode,所以自定義函數對于zend而言并沒有什么特別之處,只是將opcode進行了打包封裝。PHP腳本中函數之外的指令,整個可以認為是一個函數(或者理解為main函數更直觀)。 ```php /* function main(){ */ $a = 123; echo $a; /* } */ ``` #### 3.2.1.1 函數的存儲結構 下面具體看下PHP中函數的結構: ```c typedef union _zend_function zend_function; //zend_compile.h union _zend_function { zend_uchar type; /* MUST be the first element of this struct! */ struct { zend_uchar type; /* never used */ zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */ uint32_t fn_flags; zend_string *function_name; zend_class_entry *scope; //成員方法所屬類,面向對象實現中用到 union _zend_function *prototype; uint32_t num_args; //參數數量 uint32_t required_num_args; //必傳參數數量 zend_arg_info *arg_info; //參數信息 } common; zend_op_array op_array; //函數實際編譯為普通的zend_op_array zend_internal_function internal_function; }; ``` 這是一個union,因為PHP中函數除了用戶自定義函數還有一種:內部函數,內部函數是通過擴展或者內核提供的C函數,比如time、array系列等等,內部函數稍后再作分析。 內部函數主要用到`internal_function`,而用戶自定義函數編譯完就是一個普通的opcode數組,用的是`op_array`(注意:op_array、internal_function是嵌入的兩個結構,而不是一個單獨的指針),除了這兩個上面還有一個`type`跟`common`,這倆是做什么用的呢? 經過比較發現`zend_op_array`與`zend_internal_function`結構的起始位置都有`common`中的幾個成員,如果你對C的內存比較了解應該會馬上想到它們的用法,實際`common`可以看作是`op_array`、`internal_function`的header,不管是什么哪種函數都可以通過`zend_function.common.xx`快速訪問到`zend_function.zend_op_array.xx`及`zend_function.zend_internal_function.xx`,下面幾個,`type`同理,可以直接通過`zend_function.type`取到`zend_function.op_array.type`及`zend_function.internal_function.type`。 ![](https://box.kancloud.cn/1613b76340b7dc4098f0170e15d3d405_860x655.jpg) 函數是在編譯階段確定的,那么它們存在哪呢? 在PHP腳本的生命周期中有一個非常重要的值`executor_globals`(非ZTS下),類型是`struct _zend_executor_globals`,它記錄著PHP生命周期中所有的數據,如果你寫過PHP擴展一定用到過`EG`這個宏,這個宏實際就是對`executor_globals`的操作:`define EG(v) (executor_globals.v)` `EG(function_table)`是一個哈希表,記錄的就是PHP中所有的函數。 PHP在編譯階段將用戶自定義的函數編譯為獨立的opcodes,保存在`EG(function_table)`中,調用時重新分配新的zend_execute_data(相當于運行棧),然后執行函數的opcodes,調用完再還原到舊的`zend_execute_data`,繼續執行,關于zend引擎execute階段后面會詳細分析。 #### 3.2.1.2 函數參數 函數參數在內核實現上與函數內的局部變量實際是一樣的,上一篇我們介紹編譯的時候提供局部變量會有一個單獨的 __編號__ ,而函數的參數與之相同,參數名稱也在zend_op_array.vars中,編號首先是從參數開始的,所以按照參數順序其編號依次為0、1、2...(轉化為相對內存偏移量就是96、112、128...),然后函數調用時首先會在調用位置將參數的value復制到各參數各自的位置,詳細的傳參過程我們在執行一篇再作說明。 比如: ```php function my_function($a, $b = "aa"){ $ret = $a . $b; return $ret; } ``` 編譯完后各變量的內存偏移量編號: ``` $a => 96 $b => 112 $ret => 128 ``` 與下面這么寫一樣: ```php function my_function(){ $a = NULL; $b = "aa"; $ret = $a . $b; return $ret; } ``` 另外參數還有其它的信息,這些信息通過`zend_arg_info`結構記錄: ```c typedef struct _zend_arg_info { zend_string *name; //參數名 zend_string *class_name; zend_uchar type_hint; //顯式聲明的參數類型,比如(array $param_1) zend_uchar pass_by_reference; //是否引用傳參,參數前加&的這個值就是1 zend_bool allow_null; //是否允許為NULL,注意:這個值并不是用來表示參數是否為必傳的 zend_bool is_variadic; //是否為可變參數,即...用法,與golang的用法相同,5.6以上新增的一個用法:function my_func($a, ...$b){...} } zend_arg_info; ``` 每個參數都有一個上面的結構,所有參數的結構保存在`zend_op_array.arg_info`數組中,這里有一個地方需要注意:`zend_op_array->arg_info`數組保存的并不全是輸入參數,如果函數聲明了返回值類型則也會為它創建一個`zend_arg_info`,這個結構在arg_info數組的第一個位置,這種情況下`zend_op_array->arg_info`指向的實際是數組的第二個位置,返回值的結構通過`zend_op_array->arg_info[-1]`讀取,這里先單獨看下編譯時的處理: ```c //函數參數的編譯 void zend_compile_params(zend_ast *ast, zend_ast *return_type_ast) { zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; zend_op_array *op_array = CG(active_op_array); zend_arg_info *arg_infos; if (return_type_ast) { //聲明了返回值類型:function my_func():array{...} //多分配一個zend_arg_info arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children + 1, 0); ... arg_infos->allow_null = 0; ... //arg_infos指向了下一個位置 arg_infos++; op_array->fn_flags |= ZEND_ACC_HAS_RETURN_TYPE; } else { //沒有聲明返回值類型 if (list->children == 0) { return; } arg_infos = safe_emalloc(sizeof(zend_arg_info), list->children, 0); } ... op_array->num_args = list->children; //聲明了返回值的情況下arg_infos已經指向了數組的第二個元素 op_array->arg_info = arg_infos; } ``` #### 3.2.1.3 函數的編譯 我們在上一篇文章介紹過PHP代碼的編譯過程,主要是PHP->AST->Opcodes的轉化,上面也說了函數其實就是將一組PHP代碼編譯為單獨的opcodes,函數的調用就是不同opcodes間的切換,所以函數的編譯過程與普通PHP代碼基本一致,只是會有一些特殊操作,我們以3.2.1.2開始那個例子簡單看下編譯過程。 普通函數的語法解析規則: ```c function_declaration_statement: function returns_ref T_STRING backup_doc_comment '(' parameter_list ')' return_type '{' inner_statement_list '}' { $$ = zend_ast_create_decl(ZEND_AST_FUNC_DECL, $2, $1, $4, zend_ast_get_str($3), $6, NULL, $10, $8); } ; ``` 規則主要由五部分組成: * __returns_ref:__ 是否返回引用,在函數名前加&,比如function &test(){...} * __T_STRING:__ 函數名 * __parameter_list:__ 參數列表 * __return_type:__ 返回值類型 * __inner_statement_list:__ 函數內部代碼 函數生成的抽象語法樹根節點類型是zend_ast_decl,所有函數相關的信息都記錄在這個節點中(除了函數外類也是用的這個): ```c typedef struct _zend_ast_decl { zend_ast_kind kind; //函數就是ZEND_AST_FUNC_DECL,類則是ZEND_AST_CLASS zend_ast_attr attr; /* Unused - for structure compatibility */ uint32_t start_lineno; //函數起始行 uint32_t end_lineno; //函數結束行 uint32_t flags; //其中一個標識位用來標識返回值是否為引用,是則為ZEND_ACC_RETURN_REFERENCE unsigned char *lex_pos; zend_string *doc_comment; zend_string *name; //函數名 zend_ast *child[4]; //child有4個子節點,分別是:參數列表節點、use列表節點、函數內部表達式節點、返回值類型節點 } zend_ast_decl; ``` 上面的例子最終生成的語法樹: ![](https://box.kancloud.cn/ee9c33e2366ca443710346f161843ca9_730x552.png) 具體編譯為opcodes的過程在`zend_compile_func_decl()`中: ```c void zend_compile_func_decl(znode *result, zend_ast *ast) { zend_ast_decl *decl = (zend_ast_decl *) ast; zend_ast *params_ast = decl->child[0]; //參數列表 zend_ast *uses_ast = decl->child[1]; //use列表 zend_ast *stmt_ast = decl->child[2]; //函數內部 zend_ast *return_type_ast = decl->child[3]; //返回值類型 zend_bool is_method = decl->kind == ZEND_AST_METHOD; //是否為成員函數 //這里保存當前正在編譯的zend_op_array:CG(active_op_array),然后重新為函數生成一個新的zend_op_array, //函數編譯完再將舊的還原 zend_op_array *orig_op_array = CG(active_op_array); zend_op_array *op_array = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); //新分配zend_op_array ... if (is_method) { zend_bool has_body = stmt_ast != NULL; zend_begin_method_decl(op_array, decl->name, has_body); } else { zend_begin_func_decl(result, op_array, decl); //注意這里會在當前zend_op_array(不是新生成的函數那個)生成一條ZEND_DECLARE_FUNCTION的opcode } CG(active_op_array) = op_array; ... zend_compile_params(params_ast, return_type_ast); //編譯參數 if (uses_ast) { zend_compile_closure_uses(uses_ast); } zend_compile_stmt(stmt_ast); //編譯函數內部語法 ... pass_two(CG(active_op_array)); ... CG(active_op_array) = orig_op_array; //還原之前的 } ``` > __編譯過程主要有這么幾個處理:__ > __(1)__ 保存當前正在編譯的zend_op_array,新分配一個結構,因為每個函數、include的文件都對應獨立的一個zend_op_array,通過CG(active_op_array)記錄當前編譯所屬zend_op_array,所以開始編譯函數時就需要將這個值保存下來,等到函數編譯完成再還原回去;另外還有一個關鍵操作:`zend_begin_func_decl`,這里會在當前zend_op_array(不是新生成的函數那個)生成一條 __ZEND_DECLARE_FUNCTION__ 的opcode,也就是函數聲明操作。 ```php $a = 123; //當前為CG(active_op_array) = zend_op_array_1,編譯到這時此opcode加到zend_op_array_1 //新分配一個zend_op_array_2,并將當前CG(active_op_array)保存到origin_op_array, //然后將CG(active_op_array)=zend_op_array_2 function test(){ $b = 234; //編譯到zend_op_array_2 }//函數編譯結束,將CG(active_op_array) = origin_op_array,切回zend_op_array_1 $c = 345; //編譯到zend_op_array_1 ``` > __(2)__ 編譯參數列表,函數的參數我們在上一小節已經介紹,完整的參數會有三個組成:參數類型(可選)、參數名、默認值(可選),這三部分分別保存在參數節點的三個child節點中,編譯參數的過程有兩個關鍵操作: >> __操作1:__ 為每個參數編號 >> __操作2:__ 每個參數生成一條opcode,如果是可變參數其opcode=ZEND_RECV_VARIADIC,如果有默認值則為ZEND_RECV_INIT,否則為ZEND_RECV > 上面的例子中$a編號為96,$b為112,同時生成了兩條opcode:ZEND_RECV、ZEND_RECV_INIT,調用的時候會根據具體傳參數量跳過部分opcode,比如這個函數我們這么調用`my_function($a)`則ZEND_RECV這條opcode就直接跳過了,然后執行ZEND_RECV_INIT將默認值寫到112位置,具體的編譯過程在`zend_compile_params()`中,上面已經介紹過。 > > 參數默認值的保存與普通變量賦值相同:`$a = array()`,`array()`保存在literals,參數的默認值也是如此。 > > __(3)__ 編譯函數內部語法,這個跟普通PHP代碼編譯過程無異。 > __(4)__ pass_two(),上一篇介紹過,不再贅述。 最終生成兩個zend_op_array: ![](https://box.kancloud.cn/f174cab8d6af8b22f61d77dcedea9362_422x255.png) 總體來看,PHP在逐行編譯時發現一個function則生成一條ZEND_DECLARE_FUNCTION的opcode,然后調到函數中編譯函數,編譯完再跳回去繼續下面的編譯,這里多次提到ZEND_DECLARE_FUNCTION這個opcode是因為在函數編譯結束后還有一個重要操作:`zend_do_early_binding()`,前面我們說過總的編譯入口在`zend_compile_top_stmt()`,這里會對每條語法逐條編譯,而函數、類在編譯完成后還有后續的操作: ```c void zend_compile_top_stmt(zend_ast *ast) { ... if (ast->kind == ZEND_AST_STMT_LIST) { for (i = 0; i < list->children; ++i) { zend_compile_top_stmt(list->child[i]); } } zend_compile_stmt(ast); //編譯各條語法,函數也是在這里編譯完成 //函數編譯完成后 if (ast->kind == ZEND_AST_FUNC_DECL || ast->kind == ZEND_AST_CLASS) { CG(zend_lineno) = ((zend_ast_decl *) ast)->end_lineno; zend_do_early_binding(); } } ``` `zend_do_early_binding()`核心工作就是 __將function、class加到CG(function_table)、CG(class_table)中__ ,加入成功了就直接把 __ZEND_DECLARE_FUNCTION__ 這條opcode干掉了,加入失敗的話則保留,這個相當于 __有一部分opcode在『編譯時』提前執行了__ ,這也是為什么PHP中可以先調用函數再聲明函數的原因,比如: ```php $a = 1234; echo my_function($a); function my_function($a){ ... } ``` 實際原始的opcode以及執行順序: ![](https://box.kancloud.cn/5142256301f0a8bedd00c4d7ba696191_265x93.png) 類的情況也是如此,后面我們再作說明。 #### 3.2.1.4 匿名函數 匿名函數(Anonymous functions),也叫閉包函數(closures),允許臨時創建一個沒有指定名稱的函數。最經常用作回調函數(callback)參數的值。當然,也有其它應用的情況。 官網的示例: ```php $greet = function($name) { printf("Hello %s\r\n", $name); }; $greet('World'); $greet('PHP'); ``` 這里提匿名函數只是想說明編譯函數時那個use的用法: __匿名函數可以從父作用域中繼承變量。 任何此類變量都應該用 use 語言結構傳遞進去。__ ```php $message = 'hello'; $example = function () use ($message) { var_dump($message); }; $example(); ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看