# 6.2 引用與函數的執行結果
一個函數的執行結果要返回給調用者,除了使用return功能,還有一種辦法,那就是以引用的形式傳遞參數,然后在內部修改這個參數的值。前一種方法往往只能返回一個值,如果我們的函數執行結果具有多種數據,便需要把這些數據打包到一個數組、類等復合類型的變量中才能得以實現;但后一種方法相比而言就簡單一些了。
### 運行時傳遞引用:Call-time Pass-by-ref
標題有點繞口,其實很簡單,功能如以下php代碼所示:
````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也跟著被一起修改了。同樣的功能我們如何在擴展里實現呢,其實很簡單,請看下面的源碼:
````c
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
<?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里需要這樣:
````c
ZEND_FE(byref_compiletime, byref_compiletime_arginfo)
````
byref_compiletime_arginfo是一個arginfo結構體,我們在前面的章節中已經用過一次了。
<div class="tip-common">原書中此處有arginfo在PHP4里的實現,被我略去了。</div>
在Zend Engine 2 (PHP5+)中,arginfo的數據是由多個zend_arg_info結構體構成的數組,數組的每一個成員即每一個zend_arg_info結構體處理函數的一個參數。zend_arg_info結構體的定義如下:
````c
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擴展開發者,內核已經準備好了相應的宏來專門處理此問題,首先先用一個宏函數來生成頭部,然后用第二個宏生成具體的數據,最后用一個宏生成尾部代碼。
````c
#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的后兩個參數:
<ul>
<li>name和pass_rest_by_reference的含義同上。</li>
<li>return_reference:聲明這個函數的返回值需要以引用的形式返回,這個參數已經在前面章節用過了。</li>
<li>required_num_args:函數被調用時,傳遞參數至少為前N個函數(也就是后面參數都有默認值),當設置為-1時,必須傳遞所有參數</li>
</ul>
接下來讓我們看生成具體數據的宏:
````c
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進行特殊處理。
````c
#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就行了:
````c
ZEND_FALIAS(byref_compiletime,byref_calltime,byref_compiletime_arginfo)
````
## links
* 6.1 [一個特殊的參數:return_value](<6.1.md>)
* 6.3 [第六章小結](<6.3.md>)
- about
- 開始閱讀
- 目錄
- 1 PHP的生命周期
- 1.讓我們從SAPI開始
- 2.PHP的啟動與終止
- 3.PHP的生命周期
- 4.線程安全
- 5.小結
- 2 PHP變量在內核中的實現
- 1. 變量的類型
- 2. 變量的值
- 3. 創建PHP變量
- 4. 變量的存儲方式
- 5. 變量的檢索
- 6. 類型轉換
- 7. 小結
- 3 內存管理
- 1. 內存管理
- 2. 引用計數
- 3. 總結
- 4 動手編譯PHP
- 1. 編譯前的準備
- 2. PHP編譯前的config配置
- 3. Unix/Linux平臺下的編譯
- 4. 在Win32平臺上編譯PHP
- 5. 小結
- 5 Your First Extension
- 1. 一個擴展的基本結構
- 2. 編譯我們的擴展
- 3. 靜態編譯
- 4. 編寫函數
- 5. 小結
- 6 函數返回值
- 1. 一個特殊的參數:return_value
- 2. 引用與函數的執行結果
- 3. 小結
- 7 函數的參數
- 1. zend_parse_parameters
- 2. Arg Info 與類型綁定
- 3. 小結
- 8 使用HashTable與{數組}
- 1. 數組(C中的)與鏈表
- 2. 操作HashTable的API
- 3. 在內核中操作PHP語言中數組
- 4. 小結
- 9 PHP中的資源類型
- 1. 復合類型的數據——{資源}
- 2. Persistent Resources
- 3. {資源}自有的引用計數
- 4. 小結
- 10 PHP中的面向對象(一)
- 1. zend_class_entry
- 2. 定義一個類
- 3. 定義一個接口
- 4. 類的繼承與接口的實現
- 5. 小結
- 11 PHP中的面向對象(二)
- 1. 生成對象的實例與調用方法
- 2. 讀寫對象的屬性
- 3. 小結
- 12 啟動與終止的那點事
- 2. 小結
- 1. 關于生命周期
- 2. MINFO與phpinfo
- 3. 常量
- 4. PHP擴展中的全局變量
- 5. PHP語言中的超級全局變量
- 6. 小結
- 13 INI設置
- 1. 聲明和訪問ini設置
- 2. 小結
- 2. 小結
- 14 流式訪問
- 1. 概覽
- 2. 打開流
- 3. 訪問流
- 4. 靜態資源操作
- 5. 小結
- 15 流的實現
- 1. php流的表象之下
- 2. 包裝器操作
- 3. 實現一個包裝器
- 4. 操縱
- 5. 檢查
- 6. 小結
- 16 有趣的流
- 1. 上下文
- 2. 過濾器
- 3. 小結
- 17 配置和鏈接
- 1. autoconf
- 2. 庫的查找
- 3. 強制模塊依賴
- 4. Windows方言
- 5. 小結
- 18 擴展生成
- 1. ext_skel
- 2. PECL_Gen
- 3. 小結
- 19 設置宿主環境
- 1. 嵌入式SAPI
- 2. 構建并編譯一個宿主應用
- 3. 通過嵌入包裝重新創建cli
- 4. 老技術新用
- 5. 小結
- 20 高級嵌入式
- 1. 回調到php中
- 2. 錯誤處理
- 3. 初始化php
- 4. 覆寫INI_SYSTEM和INI_PERDIR選項
- 5. 捕獲輸出
- 6. 同時擴展和嵌入
- 7. 小結
- 約定