# 6.1 函數返回值
# 6.1 函數返回值
你也許會認為擴展中定義的函數應該直接通過return關鍵字來返回一個值,比如由你自己來生成一個zval并返回,就像下面這樣:
```
ZEND_FUNCTION(sample_long_wrong)
{
zval *retval;
MAKE_STD_ZVAL(retval);
ZVAL_LONG(retval, 42);
return retval;
}
```
但是,上面的寫法是無效的!與其讓擴展開發員每次都初始化一個zval并return之,zend引擎早就準備好了一個更好的方法。它在每個zif函數聲明里加了一個zval\*類型的形參,名為return\_value,專門來解決返回值這個問題。在前面我們已經知道了ZEND\_FUNCTION宏展開后是void name(INTERNAL\_FUNCTION\_PARAMETERS)的形式,現在是我們展開代表參數聲明的INTERNAL\_FUNCTION\_PARAMETERS宏的時候了。
```
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC
```
- int ht
- zval \*return\_value,我們在函數內部修改這個指針,函數執行完成后,內核將把這個指針指向的zval返回給用戶端的函數調用者。
- zval \*\*return\_value\_ptr,
- zval \*this\_ptr,如果此函數是一個類的方法,那么這個指針的含義和PHP語言中$this變量差不多。
- int return\_value\_used,代表用戶端在調用此函數時有沒有使用到它的返回值。
下面讓我們先試驗一個非常簡單的例子,我先給出PHP語言中的實現,然后給出我們在擴展中用C語言完成相同功能的代碼。 ````php ```` 下面是我們在編寫擴展時的實現。 ````c ZEND\_FUNCTION(sample\_long) { ZVAL\_LONG(return\_value, 42); return; } ```` 需要注意的是,ZEND\_FUNCTION本身并沒有通過return關鍵字返回任何有價值的東西,它只不過是在運行時修改了return\_value指針所指向的變量的值而已,而內核則會把return\_value指向的變量作為用戶端調用此函數后的得到的返回值。回想一下,ZVAL\_LONG()宏是對一類操作的封裝,展開后應該就是下面這樣: ````c Z\_TYPE\_P(return\_value) = IS\_LONG; Z\_LVAL\_P(return\_value) = 42; //更徹底的講,應該是這樣的: return\_value->type = IS\_LONG; return\_value->value.lval = 42; ```` 我們千萬不要自己去修改return\_value的is\_ref\_\_gc和refcount\_\_gc屬性,這兩個屬性的值會由PHP內核自動管理。
現在我們把它加到我們在第五章得到的那個擴展框架里,并把這個函數名稱注冊到函數入口數組里,就像下面這樣: ````c static zend\_function\_entry walu\_functions\[\] = { ZEND\_FE(walu\_hello, NULL) PHP\_FE(sample\_long, NULL) { NULL, NULL, NULL } }; ```` 現在我們編譯我們的擴展,便可以在用戶端通過調用sample\_long函數來得到一個整型的返回值了: ````php ```` ### 與return\_value有關的宏 return\_value如此重要,內核肯定早已經為它準備了大量的宏,來簡化我們的操作,提高程序的質量。 在前幾章我們接觸的宏大多都是以ZVAL\_開頭的,而接下來我們要介紹的宏的名字是:RETVAL。 再回到上面的那個例子,我們用RETVAL來重寫一下: ````c PHP\_FUNCTION(sample\_long) { RETVAL\_LONG(42); //展開后相當與ZVAL\_LONG(return\_value, 42); return; } ```` 大多數情況下,我們在處理完return\_value后所做的便是用return語句結束我們的函數執行,幫人幫到底,送佛送到西,為了減少我們的工作量,內核中還提供了RETURN\_\*系列宏來為我們自動補上return;如: ````c PHP\_FUNCTION(sample\_long) { RETURN\_LONG(42); //#define RETURN\_LONG(l) { RETVAL\_LONG(l); return; } php\_printf("I will never be reached.\\n"); //這一行代碼永遠不會被執行。 } ```` 下面,我們給出目前所有的RETVAL\_\*\*\*宏和RETURN\_\*\*\*宏,供大家查閱使用。 ````c //這些宏都定義在Zend/zend\_API.h文件里 #define RETVAL\_RESOURCE(l) ZVAL\_RESOURCE(return\_value, l) #define RETVAL\_BOOL(b) ZVAL\_BOOL(return\_value, b) #define RETVAL\_NULL() ZVAL\_NULL(return\_value) #define RETVAL\_LONG(l) ZVAL\_LONG(return\_value, l) #define RETVAL\_DOUBLE(d) ZVAL\_DOUBLE(return\_value, d) #define RETVAL\_STRING(s, duplicate) ZVAL\_STRING(return\_value, s, duplicate) #define RETVAL\_STRINGL(s, l, duplicate) ZVAL\_STRINGL(return\_value, s, l, duplicate) #define RETVAL\_EMPTY\_STRING() ZVAL\_EMPTY\_STRING(return\_value) #define RETVAL\_ZVAL(zv, copy, dtor) ZVAL\_ZVAL(return\_value, zv, copy, dtor) #define RETVAL\_FALSE ZVAL\_BOOL(return\_value, 0) #define RETVAL\_TRUE ZVAL\_BOOL(return\_value, 1) #define RETURN\_RESOURCE(l) { RETVAL\_RESOURCE(l); return; } #define RETURN\_BOOL(b) { RETVAL\_BOOL(b); return; } #define RETURN\_NULL() { RETVAL\_NULL(); return;} #define RETURN\_LONG(l) { RETVAL\_LONG(l); return; } #define RETURN\_DOUBLE(d) { RETVAL\_DOUBLE(d); return; } #define RETURN\_STRING(s, duplicate) { RETVAL\_STRING(s, duplicate); return; } #define RETURN\_STRINGL(s, l, duplicate) { RETVAL\_STRINGL(s, l, duplicate); return; } #define RETURN\_EMPTY\_STRING() { RETVAL\_EMPTY\_STRING(); return; } #define RETURN\_ZVAL(zv, copy, dtor) { RETVAL\_ZVAL(zv, copy, dtor); return; } #define RETURN\_FALSE { RETVAL\_FALSE; return; } #define RETURN\_TRUE { RETVAL\_TRUE; return; } ```` 其實,除了這些標量類型,還有很多php語言中的復合類型我們需要在函數中返回,如數組和對象,我們可以通過RETVAL\_ZVAL與RETURN\_ZVAL來操作它們,有關它們的詳細介紹我們將在后續章節中敘述。 ### 不返回值可以么? 其實,zend internal function的形參中還有一個比較常用的名為return\_value\_used的參數,它是干嘛使的呢?它用來標志這個函數的返回值在用戶端有沒有用到。看下面的代碼: ````php 5) || (PHP\_MAJOR\_VERSION == 5 && PHP\_MINOR\_VERSION > 0) ZEND\_FUNCTION(return\_by\_ref) { zval \*\*a\_ptr; zval \*a; //檢查全局作用域中是否有$a這個變量,如果沒有則添加一個 //在內核中真的是可以胡作非為啊,:-) if(zend\_hash\_find(&EG(symbol\_table) , "a",sizeof("a"),(void \*\*)&a\_ptr ) == SUCCESS ) { a = \*a\_ptr; } else { ALLOC\_INIT\_ZVAL(a); zend\_hash\_add(&EG(symbol\_table), "a", sizeof("a"), &a,sizeof(zval\*), NULL); } //廢棄return\_value,使用return\_value\_ptr來接替它的工作 zval\_ptr\_dtor(return\_value\_ptr); if( !a->is\_ref\_\_gc && a->refcount\_\_gc > 1 ) { zval \*tmp; MAKE\_STD\_ZVAL(tmp); \*tmp = \*a; zval\_copy\_ctor(tmp); tmp->is\_ref\_\_gc = 0; tmp->refcount\_\_gc = 1; zend\_hash\_update(&EG(symbol\_table), "a", sizeof("a"), &tmp,sizeof(zval\*), NULL); a = tmp; } a->is\_ref\_\_gc = 1; a->refcount\_\_gc++; \*return\_value\_ptr = a; } #endif /\* PHP >= 5.1.0 \*/ ```` return\_value\_ptr是定義zend internal function時的另外一個重要參數,他是一個zval\*\*類型的指針,并且指向函數的返回值。我們調用zval\_ptr\_dtor()函數后,默認的return\_value便被廢棄了。這里的$a變量如果是與某個非引用形式的變量共用一個zval的話,便要進行分離。 不幸的是,如果你編譯上面的代碼,使用的時候便會得到一個段錯誤。為了使它能夠正常的工作,需要在源文件中加一些東西: ````c #if (PHP\_MAJOR\_VERSION > 5) || (PHP\_MAJOR\_VERSION == 5 && PHP\_MINOR\_VERSION > 0) ZEND\_BEGIN\_ARG\_INFO\_EX(return\_by\_ref\_arginfo, 0, 1, 0) ZEND\_END\_ARG\_INFO () #endif /\* PHP >= 5.1.0 \*/ 然后使用下面的代碼來申明我們的定義的函數: #if (PHP\_MAJOR\_VERSION > 5) || (PHP\_MAJOR\_VERSION == 5 && PHP\_MINOR\_VERSION > 0) ZEND\_FE(return\_by\_ref, return\_by\_ref\_arginfo) #endif /\* PHP >= 5.1.0 \*/ ```` arginfo是一種特殊的結構體,用來提前向內核告知此函數具有的一些特定的性質,如本例,其將告訴內核本函數需要引用形式的返回值,所以內核不再通過return\_value來獲取執行結果,而是通過return\_value\_ptr。如果沒有arginfo,那內核會預先把return\_value\_ptr置為NULL,當我們對其調用zval\_ptr\_dtor()函數時便會使程序崩潰。 這一些代碼都包含在了一個宏里面,只有在php版本大于等于5.1的時候才會被啟用。如果沒有這些if、endif,那我們的程序將無法在php4下通過編譯,在php5.0上也會激活一些無法預測的錯誤。
- - - - - -
- zone(zqx10104#163.com)于2011-10-20提供了一個Bug,:-)
## links
- 6 [函數返回值](6.html)
- 6.2 [引用與函數的執行結果](6.2.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 小結