前面小節中對函數的內部表示以及參數的傳遞,返回值都有了介紹,那函數是怎么被調用的呢?內置函數和用戶定義函數在調用時會有什么不一樣呢?下面將介紹函數調用和執行的過程。
## 函數的調用[]()
函數被調用需要一些基本的信息,比如函數的名稱,參數以及函數的定義(也就是函數的具體執行內容),從我們開發者的角度來看,定義了一個函數我們在執行的時候自然知道這個函數叫什么名字,以及調用的時候給傳遞了什么參數、函數的操作內容。但是對于Zend引擎不能像我們這樣能“看懂”php源代碼,他它需要對代碼進行處理以后才能執行。我們還是從以下兩個小例子開始:
<?php
function foo(){
echo "I'm foo!";
}
foo();
?>
下面我們先看一下其對應的opcodes:
function name: (null)
line # * op fetch ext return operands
---------------------------------------------------------------------------------
DO_FCALL 0 'foo'
NOP
> RETURN 1
?
function name: foo
line # * op fetch ext return operands
---------------------------------------------------------------------------------
4 0 > echo 'I%27m+foo%21'
5 1 > RETURN null
上面是去除了一些枝節信息的的opcodes,可以看到執行時函數部分的opcodes是單獨獨立出來的,這點對于函數的執行特別重要,下面的部分會詳細介紹。現在,我們把焦點放到對foo函數的調用上面。調用foo的OPCODE是“DO_FCALL“, DO_FCALL進行函數調用操作時,ZE會在function_table中根據函數名(如前所述,這里的函數名經過str_tolower的處理,所以PHP的函數名大小寫不敏感)查找函數的定義, 如果不存在,則報出“Call to undefined function xxx()"的錯誤信息; 如果存在,就返回該函數zend_function結構指針,然后通過function.type的值來判斷函數是內部函數還是用戶定義的函數,調用zend_execute_internal(zend_internal_function.handler)或者直接 調用zend_execute來執行這個函數包含的zend_op_array。
## 函數的執行[]()
細心的讀者可能會注意到上面opcodes里函數被調用的時候以及函數定義那都有個"function name:",其實用戶定義函數的執行與其他語句的執行并無區別,在本質上看,其實函數中的php語句與函數外的php語句并無不同。函數體本身最大的區別,在于其執行環境的不同。這個“執行環境”最重要的特征就是變量的作用域。大家都知道,函數內定義的變量在函數體外是無法直接使用的,反之也是一樣。那么,在函數執行的時候,進入函數前的環境信息是必須要保存的。在函數執行完畢后,這些環境信息也會被還原,使整個程序繼續的執行下去。
內部函數的執行與用戶函數不同。用戶函數是php語句一條條“翻譯”成op_line組成的一個op_array,而內部函數則是用C來實現的,因為執行環境也是C環境,所以可以直接調用。如下面的例子:
[php]
<?php
$foo = 'test';
print_r($foo);
?>
對應的opcodes也很簡單:
line # * op fetch ext return operands
---------------------------------------------------------------------------------
2 0 > ASSIGN !0, 'test'
3 1 SEND_VAR !0
2 DO_FCALL 1 'print_r'
4 3 > RETURN 1
可以看出,生成的opcodes中,內部函數和用戶函數的處理都是由DO_FCALL來進行的。而在其具體實現的zend_do_fcall_common_helper_SPEC()中,則對是否為內部函數進行了判斷,如果是內部函數,則使用一個比較長的調用
((zend_internal_function *) EX(function_state).function)->handler(opline->extended_value, EX_T(opline->result.u.var).var.ptr, EX(function_state).function->common .return_reference?&EX_T(opline->result.u.var).var.ptr:NULL, EX(object), RETURN_VALUE_USED(opline) TSRMLS_CC);
上面這種方式的內部函數是在zend_execute_internal函數沒有定義的情況下。而在而在Zend/zend.c文件的zend_startup函數中,
zend_execute_internal = NULL;
此函數確實被賦值為NULL。于是我們在if (!zend_execute_internal)判斷時會成立,所以我們是執行那段很長的調用。那么,這段很長的調用到底是什么呢?以我們常用的 **count**函數為例。在[<<第一節 函數的內部結構>>](#)中,我們知道內部函數所在的結構體中有一個handler指針指向此函數需要調用的內部定義的C函數。這些內部函數在模塊初始化時就以擴展的函數的形式加載到EG(function_table)。其調用順序:
php_module_startup --> php_register_extensions --> zend_register_internal_module
--> zend_register_module_ex --> zend_register_functions
?
zend_register_functions(NULL, module->functions, NULL, module->type TSRMLS_CC)
在standard擴展中。module的定義為:
zend_module_entry basic_functions_module = {
STANDARD_MODULE_HEADER_EX,
NULL,
standard_deps,
"standard", /* extension name */
basic_functions, /* function list */
... //省略
}
從上面的代碼可以看出,module->functions是指向basic_functions。在basic_functions.c文件中查找basic_functions的定義。
const zend_function_entry basic_functions[] = {
...// 省略
PHP_FE(count, arginfo_count)
...//省略
}
?
#define PHP_FE ZEND_FE
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FN(name) zif_##name
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },
綜合上面的代碼,count函數最后調用的函數名為zif_count,但是此函數對外的函數名還是為count。調用的函數名name以第二個元素存放在zend_function_entry結構體數組中。對于zend_function_entry的結構
typedef struct _zend_function_entry {
const char *fname;
void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
const struct _zend_arg_info *arg_info;
zend_uint num_args;
zend_uint flags;
} zend_function_entry;
第二個元素為handler。這也就是我們在執行內部函數時的調用方法。因此在執行時就會調用到對應的函數。
對于用戶定義的函數,在zend_do_fcall_common_helper_SPEC()函數中,
if (EX(function_state).function->type == ZEND_USER_FUNCTION ||
EX(function_state).function->common.scope) {
should_change_scope = 1;
EX(current_this) = EG(This);
EX(current_scope) = EG(scope);
EX(current_called_scope) = EG(called_scope);
EG(This) = EX(object);
EG(scope) = (EX(function_state).function->type == ZEND_USER_FUNCTION || !EX(object)) ? EX(function_state).function->common.scope : NULL;
EG(called_scope) = EX(called_scope);
}
先將EG下的This,scope等暫時緩存起來(這些在后面會都恢復到此時緩存的數據)。在此之后,對于用戶自定義的函數,程序會依據zend_execute是否等于execute并且是否為異常來判斷是返回,還是直接執行函數定義的op_array:
if (zend_execute == execute && !EG(exception)) {
EX(call_opline) = opline;
ZEND_VM_ENTER();
} else {
zend_execute(EG(active_op_array) TSRMLS_CC);
}
而在Zend/zend.c文件的zend_startup函數中,已將zend_execute賦值為:
zend_execute = execute;
從而對于異常,程序會拋出異常;其它情況,程序會調用execute執行此函數中生成的opcodes。execute函數會遍歷所傳遞給它的zend_op_array數組,以方式
ret = EX(opline)->handler(execute_data TSRMLS_CC)
調用每個opcode的處理函數。而execute_data在execute函數開始時就已經給其分配了空間,這就是這個函數的執行環境。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊