在PHP中,函數有自己的作用域,同時在其內部可以實現各種語句的執行,最后返回最終結果值。在PHP的源碼中可以發現,Zend引擎將函數分為以下類型:
#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將內置的函數和用戶定義的函數分別保存。
### 1.用戶函數(ZEND_USER_FUNCTION)[]()
用戶自定義函數是非常常用的函數種類,如下面的代碼,定義了一個用戶自定義的函數:
<?php
?
function tipi( $name ){
$return = "Hi! " . $name;
[echo](http://www.php.net/echo) $return;
return $return;
}
?
?>
這個示例中,對自定義函數傳入了一個參數,并將其與_Hi!_ 一起輸出并做為返回值返回。從這個例子可以看出函數的基本特點:運行時聲明、可以傳參數、有值返回。當然,有些函數只是進行一些操作,并不一定顯式的有返回值,在PHP的實現中,即使沒有顯式的返回,Zend引擎也會“幫你“返回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擴展中查看的關于函數的信息可以看出,函數的定義和執行是分開的,一個函數可以作為一個獨立的運行單元而存在。
### 2.內部函數(ZEND_INTERNAL_FUNCTION)[]()
ZEND_INTERNAL_FUNCTION函數是由擴展、PHP內核、Zend引擎提供的內部函數,一般用“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字段作為幾種內部函數的區分。
### 3.變量函數[]()
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](http://www.php.net/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);
}
如果不是方法,并且不是動態調用,并且函數名為字符串常量,則其生成的中間代碼為ZEND_DO_FCALL。其它情況則為ZEND_DO_FCALL_BY_NAME。另外將變量函數作為回調函數,其處理過程在Zend/zend_complie.c文件的zend_do_pass_param函數中。最終會體現在中間代碼執行過程中的 **ZEND_SEND_VAL_SPEC_CONST_HANDLER** 等函數中。
### 4.匿名函數[]()
匿名函數是一類不需要指定表示符,而又可以被調用的函數或子例程,匿名函數可以方便的作為參數傳遞給其他函數,關于匿名函數的詳細信息請閱讀 [<<第四節 匿名函數及閉包>>](#)
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和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中文手冊