# 6.2 引用與函數的執行結果
# 6.2 引用與函數的執行結果
一個函數的執行結果要返回給調用者,除了使用return功能,還有一種辦法,那就是以引用的形式傳遞參數,然后在內部修改這個參數的值。前一種方法往往只能返回一個值,如果我們的函數執行結果具有多種數據,便需要把這些數據打包到一個數組、類等復合類型的變量中才能得以實現;但后一種方法相比而言就簡單一些了。
### 運行時傳遞引用:Call-time Pass-by-ref
標題有點繞口,其實很簡單,功能如以下php代碼所示:
```
<?php
function byref_calltime($a) {
$a = '(modified by ref!)';
}
$foo = 'I am a string';
//使用&傳遞引用
byref_calltime(&$foo);
echo $foo;
//輸出'(modified by ref!)'
```
我們在傳遞參數的時候使用&操作符,便可以傳遞$foo變量的引用過去,而不是copy一份。當我們在函數內核修改這個參數時,函數外部的$foo也跟著被一起修改了。同樣的功能我們如何在擴展里實現呢,其實很簡單,請看下面的源碼:
```
ZEND_FUNCTION(byref_calltime)
{
zval *a;
//將我們接收的參數傳給zval *a;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &a) == FAILURE)
{
RETURN_NULL();
}
//如果a不是以引用的方式傳遞的。
if (!a->is_ref__gc)
{
return;
}
//將a轉成字符串
convert_to_string(a);
//更改數據
ZVAL_STRING(a," (modified by ref!)",1);
return;
}
```
### 編譯時的傳遞引用Compile-time Pass-by-ref
如果每一次都在調用函數時候都對參數加一個&符號真是太羅嗦了,有沒有一個簡單的辦法呢,比如在定義函數的時候便聲明這個參數是引用形式的,而不用用戶自己加&符號表示引用,而由內核來完成這步操作?這個功能是有的,我們在PHP語言中可以這樣實現。
```
<?php
在定義函數參數的時候加了引用符
function byref_compiletime(&$a) {
$a = ' (modified by ref!)';
}
$foo = 'I am a string';
//這個地方我們沒有加&引用符
byref_compiletime($foo);
echo $foo;
//輸出 (modified by ref!)
```
上面的代碼中,我們只是把引用符號從函數調用里轉移到函數定義里。此功能在擴展里面實現的話就頗費周折了,我們需要提前為它定義一個arginfo結構體來向內核通知此函數的這個特定行為。添加此函數到module\_entry里需要這樣:
```
ZEND_FE(byref_compiletime, byref_compiletime_arginfo)
```
byref\_compiletime\_arginfo是一個arginfo結構體,我們在前面的章節中已經用過一次了。
原書中此處有arginfo在PHP4里的實現,被我略去了。
在Zend Engine 2 (PHP5+)中,arginfo的數據是由多個zend\_arg\_info結構體構成的數組,數組的每一個成員即每一個zend\_arg\_info結構體處理函數的一個參數。zend\_arg\_info結構體的定義如下:
```
typedef struct _zend_arg_info {
const char *name; /* 參數的名稱*/
zend_uint name_len; /* 參數名稱的長度*/
const char *class_name; /* 類名 */
zend_uint class_name_len; /* 類名長度*/
zend_bool array_type_hint; /* 數組類型提示 */
zend_bool allow_null; /* 是否允許為NULL */
zend_bool pass_by_reference; /* 是否引用傳遞 */
zend_bool return_reference; /* 返回值是否為引用形式 */
int required_num_args; /* 必要參數的數量 */
} zend_arg_info;
```
生成zend\_arg\_info結構的數組比較繁瑣,為了方便PHP擴展開發者,內核已經準備好了相應的宏來專門處理此問題,首先先用一個宏函數來生成頭部,然后用第二個宏生成具體的數據,最后用一個宏生成尾部代碼。
```
#define ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference) ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, ZEND_RETURN_VALUE, -1)
#define ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference, required_num_args) \
static const zend_arg_info name[] = { \
{ NULL, 0, NULL, 0, 0, 0, pass_rest_by_reference, return_reference, required_num_args },
#define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, 0, allow_null, pass_by_ref, 0, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, 1, allow_null, pass_by_ref, 0, 0 },
#define ZEND_END_ARG_INFO() };
//這里我們先看
ZEND_BEGIN_ARG_INFO(name, pass_rest_by_reference)
ZEND_BEGIN_ARG_INFO_EX(name, pass_rest_by_reference, return_reference,required_num_args)
```
這兩個宏函數的前兩個參數的含義是一樣的,name便是這個zend\_arg\_info數組變量的名字,這里我們定義它為:byref\_compiletime\_arginfo。pass\_rest\_by\_reference如果被賦值為1,則代表著所有的參數默認都是需要以引用的方式傳遞的(在arginfo中單獨聲明的除外)。而對于ZEND\_BEGIN\_ARG\_INFO\_EX的后兩個參數:
- name和pass\_rest\_by\_reference的含義同上。
- return\_reference:聲明這個函數的返回值需要以引用的形式返回,這個參數已經在前面章節用過了。
- required\_num\_args:函數被調用時,傳遞參數至少為前N個函數(也就是后面參數都有默認值),當設置為-1時,必須傳遞所有參數
接下來讓我們看生成具體數據的宏:
```
ZEND_ARG_PASS_INFO(by_ref)
//強制所有參數使用引用的方式傳遞
ZEND_ARG_INFO(by_ref, name)
//如果by_ref為1,則名稱為name的參數必須以引用的方式傳遞,
ZEND_ARG_ARRAY_INFO(by_ref, name, allow_null)
ZEND_ARG_OBJ_INFO(by_ref, name, classname, allow_null)
這兩個宏實現了類型綁定,也就是說我們在傳遞某個參數時,必須是數組類型或者某個類的實例。如果最后的參數為真,則除了綁定的數據類型,還可以傳遞一個NULL數據。
//我們組合起來使用:
ZEND_BEGIN_ARG_INFO(byref_compiletime_arginfo, 0)
ZEND_ARG_PASS_INFO(1)
ZEND_END_ARG_INFO()
```
為了使我們的擴展能夠兼容PHP4,還需要使用#ifdef進行特殊處理。
```
#ifdef ZEND_ENGINE_2
ZEND_BEGIN_ARG_INFO(byref_compiletime_arginfo, 0)
ZEND_ARG_PASS_INFO(1)
ZEND_END_ARG_INFO()
#else /* ZE 1 */
static unsigned char byref_compiletime_arginfo[] = { 1, BYREF_FORCE };
#endif
```
我們copy一份ZEND\_FUNCTION(byref\_calltime)的實現,并重名成ZEND\_FUNCTION(byref\_compiletime)就行了。或者直接弄個ZEND\_FALIAS就行了:
```
ZEND_FALIAS(byref_compiletime,byref_calltime,byref_compiletime_arginfo)
```
## links
- 6.1 [一個特殊的參數:return\_value](6.1.html)
- 6.3 [第六章小結](6.3.html)
- 介紹
- 1 PHP的生命周期
- 1.1 讓我們從SAPI開始
- 1.2 PHP的啟動與終止
- 1.3 PHP的生命周期
- 1.4 線程安全
- 1.5 PHP的生命周期
- 2 PHP變量在內核中的實現
- 2.1 變量的類型
- 2.2 變量的值
- 2.3 創建PHP變量
- 2.4 變量的存儲方式
- 2.5 變量的檢索
- 2.6 類型轉換
- 2.7 小結
- 3 內存管理
- 3.1 內存管理
- 3.2 引用計數
- 3.3 內存管理
- 4 動手編譯PHP
- 4.1 動手編譯PHP
- 4.2 動手編譯PHP
- 4.3 Unix/Linux平臺下的編譯
- 4.4 在Win32平臺上編譯PHP
- 4.5 動手編譯PHP
- 5 Your First Extension
- 5.1 Your First Extension
- 5.2 編譯我們的擴展
- 5.3 靜態編譯
- 5.4 編寫函數
- 5.5 Your First Extension
- 6 函數返回值
- 6.1 函數返回值
- 6.2 引用與函數的執行結果
- 6.3 函數返回值
- 7 函數的參數
- 7.1 函數的參數
- 7.2 函數的參數
- 7.3 函數的參數
- 8 使用HashTable與{數組}
- 8.1 使用HashTable與{數組}
- 8.2 使用HashTable與{數組}
- 8.3 使用HashTable與{數組}
- 8.4 使用HashTable與{數組}
- 9 PHP中的資源類型
- 9.1 PHP中的資源類型
- 9.2 PHP中的資源類型
- 9.3 PHP中的資源類型
- 9.4 PHP中的資源類型
- 10 PHP中的面向對象(一)
- 10.1 PHP中的面向對象(一)
- 10.2 PHP中的面向對象(一)
- 10.3 PHP中的面向對象(一)
- 10.4 PHP中的面向對象(一)
- 10.5 PHP中的面向對象(一)
- 11 PHP中的面向對象(二)
- 11.1 PHP中的面向對象(二)
- 11.2 PHP中的面向對象(二)
- 11.3 PHP中的面向對象(二)
- 12 啟動與終止的那點事
- 12.1 關于生命周期
- 12.2 MINFO與phpinfo
- 12.3 常量
- 12.4 PHP擴展中的全局變量
- 12.5 PHP語言中的超級全局變量(Superglobals)
- 12.6 小結
- 13 INI設置
- 13.1 聲明和訪問INI設置
- 13.2 小結
- 14 流式訪問
- 14.1 流的概覽
- 14.2 訪問流
- 14.3 靜態資源操作
- 14.4 links
- 15 流的實現
- 15.1 php流的表象之下
- 15.2 包裝器操作
- 15.3 實現一個包裝器
- 15.4 操縱
- 15.5 檢查
- 15.6 小結
- 16 有趣的流
- 16.1 上下文
- 16.2 過濾器
- 16.3 小結
- 17 配置和鏈接
- 17.1 autoconf
- 17.2 庫的查找
- 17.3 強制模塊依賴
- 17.4 Windows方言
- 17.5 小結
- 18 擴展生成
- 18.1 ext_skel
- 18.2 PECL_Gen
- 18.3 小結
- 19 設置宿主環境
- 19.1 嵌入式SAPI
- 19.2 構建并編譯一個宿主應用
- 19.3 通過嵌入包裝重新創建cli
- 19.4 老技術新用
- 19.5 小結
- 20 高級嵌入式
- 20.1 回調到php中
- 20.2 錯誤處理
- 20.3 初始化php
- 20.4 覆寫INI_SYSTEM和INI_PERDIR選項
- 20.5 捕獲輸出
- 20.6 同時擴展和嵌入
- 20.7 小結