在編程語言中,一個函數或一個方法一般都有返回值,但也存在不返回值的情況,此時,這些函數僅僅是處理一些事務,沒有返回,或者說沒有明確的返回值,在pascal語言中它有一個專有的關鍵字 **procedure** 。在PHP中,函數都有返回值,分兩種情況,使用return語句明確的返回和沒有return語句返回NULL。
### return語句[]()
當使用return語句時,PHP給用戶自定義的函數返回指定類型的變量。依舊我們查看源碼的方式,對return 關鍵字進行詞法分析和語法分析后,生成中間代碼。從 Zend/zend_language_parser.y文件中可以確認其生成中間代碼調用的是 **zend_do_return** 函數。
void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC) /* {{{ */
{
zend_op *opline;
int start_op_number, end_op_number;
?
if (do_end_vparse) {
if (CG(active_op_array)->return_reference
&& !zend_is_function_or_method_call(expr)) {
zend_do_end_variable_parse(expr, BP_VAR_W, 0 TSRMLS_CC);/* 處理返回引用 */
} else {
zend_do_end_variable_parse(expr, BP_VAR_R, 0 TSRMLS_CC);/* 處理常規變量返回 */
}
}
?
...// 省略 取其它中間代碼操作
?
opline->opcode = ZEND_RETURN;
?
if (expr) {
opline->op1 = *expr;
?
if (do_end_vparse && zend_is_function_or_method_call(expr)) {
opline->extended_value = ZEND_RETURNS_FUNCTION;
}
} else {
opline->op1.op_type = IS_CONST;
INIT_ZVAL(opline->op1.u.constant);
}
?
SET_UNUSED(opline->op2);
}
/* }}} */
生成中間代碼為 **ZEND_RETURN**。 第一個操作數的類型在返回值為可用的表達式時,其類型為表達式的操作類型,否則類型為 IS_CONST。這在后續計算執行中間代碼函數時有用到。根據操作數的不同,ZEND_RETURN中間代碼會執行 ZEND_RETURN_SPEC_CONST_HANDLER,ZEND_RETURN_SPEC_TMP_HANDLER或ZEND_RETURN_SPEC_TMP_HANDLER。這三個函數的執行流程基本類似,包括對一些錯誤的處理。這里我們以ZEND_RETURN_SPEC_CONST_HANDLER為例說明函數返回值的執行過程:
static int ZEND_FASTCALL ZEND_RETURN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zval *retval_ptr;
zval **retval_ptr_ptr;
?
?
if (EG(active_op_array)->return_reference == ZEND_RETURN_REF) {
?
// 返回引用時不允許常量和臨時變量
if (IS_CONST == IS_CONST || IS_CONST == IS_TMP_VAR) {
/* Not supposed to happen, but we'll allow it */
zend_error(E_NOTICE, "Only variable references \ should be returned by reference");
goto return_by_value;
}
?
retval_ptr_ptr = NULL; // 返回值
?
if (IS_CONST == IS_VAR && !retval_ptr_ptr) {
zend_error_noreturn(E_ERROR, "Cannot return string offsets by reference");
}
?
if (IS_CONST == IS_VAR && !Z_ISREF_PP(retval_ptr_ptr)) {
if (opline->extended_value == ZEND_RETURNS_FUNCTION &&
EX_T(opline->op1.u.var).var.fcall_returned_reference) {
} else if (EX_T(opline->op1.u.var).var.ptr_ptr ==
&EX_T(opline->op1.u.var).var.ptr) {
if (IS_CONST == IS_VAR && !0) {
/* undo the effect of get_zval_ptr_ptr() */
PZVAL_LOCK(*retval_ptr_ptr);
}
zend_error(E_NOTICE, "Only variable references \ should be returned by reference");
goto return_by_value;
}
}
?
if (EG(return_value_ptr_ptr)) { // 返回引用
SEPARATE_ZVAL_TO_MAKE_IS_REF(retval_ptr_ptr); // is_ref__gc設置為1
Z_ADDREF_PP(retval_ptr_ptr); // refcount__gc計數加1
?
(*EG(return_value_ptr_ptr)) = (*retval_ptr_ptr);
}
} else {
return_by_value:
?
retval_ptr = &opline->op1.u.constant;
?
if (!EG(return_value_ptr_ptr)) {
if (IS_CONST == IS_TMP_VAR) {
?
}
} else if (!0) { /* Not a temp var */
if (IS_CONST == IS_CONST ||
EG(active_op_array)->return_reference == ZEND_RETURN_REF ||
(PZVAL_IS_REF(retval_ptr) && Z_REFCOUNT_P(retval_ptr) > 0)) {
zval *ret;
?
ALLOC_ZVAL(ret);
INIT_PZVAL_COPY(ret, retval_ptr); // 復制一份給返回值
zval_copy_ctor(ret);
*EG(return_value_ptr_ptr) = ret;
} else {
*EG(return_value_ptr_ptr) = retval_ptr; // 直接賦值
Z_ADDREF_P(retval_ptr);
}
} else {
zval *ret;
?
ALLOC_ZVAL(ret);
INIT_PZVAL_COPY(ret, retval_ptr); // 復制一份給返回值
*EG(return_value_ptr_ptr) = ret;
}
}
?
return zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); // 返回前執行收尾工作
}
函數的返回值在程序執行時存儲在 *EG(return_value_ptr_ptr)。ZE內核對值返回和引用返回作了區分,并且在此基礎上對常量,臨時變量和其它類型的變量在返回時進行了不同的處理。在return執行完之前,ZE內核通過調用zend_leave_helper_SPEC函數,清除函數內部使用的變量等。這也是ZE內核自動給函數加上NULL返回的原因之一。
### 沒有return語句的函數[]()
在PHP中,沒有過程這個概念,只有沒有返回值的函數。但是對于沒有返回值的函數,PHP內核會“幫你“加上一個NULL來做為返回值。這個“幫你”的操作也是在生成中間代碼時進行的。在每個函數解析時都需要執行函數 **zend_do_end_function_declaration**,在此函數中有一條語句:
zend_do_return(NULL, 0 TSRMLS_CC);
結合前面的內容,我們知道這條語句的作用就是返回NULL。這就是沒有return語句的函數返回NULL的原因所在。
#### 內部函數的返回值[]()
內部函數的返回值都是通過一個名為 return_value 的變量傳遞的。這個變量同時也是函數中的一個參數,在PHP_FUNCTION函數擴展開來后可以看到。這個參數總是包含有一個事先申請好空間的 zval 容器,因此你可以直接訪問其成員并對其進行修改而無需先對 return_value 執行一下 MAKE_STD_ZVAL 宏指令。為了能夠更方便從函數中返回結果,也為了省卻直接訪問 zval 容器內部結構的麻煩,ZEND 提供了一大套宏命令來完成相關的這些操作。這些宏命令會自動設置好類型和數值。
**從函數直接返回值的宏:**
- RETURN_RESOURCE(resource) 返回一個資源。
- RETURN_BOOL(bool) 返回一個布爾值。
- RETURN_NULL() 返回一個空值。
- RETURN_LONG(long) 返回一個長整數。
- RETURN_DOUBLE(double) 返回一個雙精度浮點數。
- RETURN_STRING(string, duplicate) 返回一個字符串。duplicate 表示這個字符是否使用 estrdup() 進行復制。
- RETURN_STRINGL(string, length, duplicate) 返回一個定長的字符串。其余跟 RETURN_STRING 相同。這個宏速度更快而且是二進制安全的。
- RETURN_EMPTY_STRING() 返回一個空字符串。
- RETURN_FALSE 返回一個布爾值假。
- RETURN_TRUE 返回一個布爾值真。
**設置函數返回值的宏:**
- RETVAL_RESOURCE(resource) 設定返回值為指定的一個資源。
- RETVAL_BOOL(bool) 設定返回值為指定的一個布爾值。
- RETVAL_NULL 設定返回值為空值
- RETVAL_LONG(long) 設定返回值為指定的一個長整數。
- RETVAL_DOUBLE(double) 設定返回值為指定的一個雙精度浮點數。
- RETVAL_STRING(string, duplicate) 設定返回值為指定的一個字符串,duplicate 含義同 RETURN_STRING。
- RETVAL_STRINGL(string, length, duplicate) 設定返回值為指定的一個定長的字符串。其余跟 RETVAL_STRING 相同。這個宏速度更快而且是二進制安全的。
- RETVAL_EMPTY_STRING 設定返回值為空字符串。
- RETVAL_FALSE 設定返回值為布爾值假。
- RETVAL_TRUE 設定返回值為布爾值真。
如果需要返回的是像數組和對象這樣的復雜類型的數據,那就需要先調用 array_init() 和 object_init(),也可以使用相應的 hash 函數直接操作 return_value。由于這些類型主要是由一些雜七雜八的東西構成,所以對它們就沒有了相應的宏。
關于內部函數的return_value值是如何賦值給*EG(return_value_ptr_ptr),函數的調用是如何進行的,請閱讀下一小節 [<<函數的調用和執行>>](#).
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和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中文手冊