當前位置:才華齋>計算機>php語言>

PHP內部函式的定義

php語言 閱讀(8.17K)

PHP(外文名:PHP: Hypertext Preprocessor,中文名:“超文字前處理器”)是一種通用開源指令碼語言。語法吸收了C語言、Java和Perl的特點,利於學習,使用廣泛,主要適用於Web開發領域。那麼PHP內部函式的定義是怎樣的呢?以下僅供參考!

PHP內部函式的定義

如何找到函式的定義

作為開始,讓我們嘗試找出strpos函式的定義。

嘗試的第一步,就是去PHP 5.4根目錄然後在頁面頂部的搜尋框輸入strpos。搜尋的結果是一個很大的列表,展示了strpos在PHP原始碼中出現的位置。

因為這個結果對我們並沒有太大的幫助,我們使用一個小技巧:我們搜尋”PHP_FUNCTION strpos”(不要漏了雙引號,它們很重要),而不是strpos.

現在我們得到兩個入口連結:

/PHP_5_4/ext/standard/

php_string.h 48 PHP_FUNCTION(strpos);

string.c 1789 PHP_FUNCTION(strpos)

第一個要注意的事情是,兩個位置都是在ext/standard資料夾。這就是我們希望找到的,因為strpos函式(跟大部分string,array和檔案函式一樣)是standard擴充套件的一部分。

現在,在新標籤頁開啟兩個連結,然後看看它們背後藏了什麼程式碼。

你會看到第一個連結帶你到了php_string.h檔案,它包含了下面的程式碼:

// ...

PHP_FUNCTION(strpos);

PHP_FUNCTION(stripos);

PHP_FUNCTION(strrpos);

PHP_FUNCTION(strripos);

PHP_FUNCTION(strrchr);

PHP_FUNCTION(substr);

// ...

這就是一個典型的標頭檔案(以.h字尾結尾的檔案)的樣子:單純的函式列表,函式在其他地方定義。事實上,我們對這些並不感興趣,因為我們已經知道我們要找的是什麼。

第二個連結更有趣:它帶我們到string.c檔案,這個檔案包含了函式真正的原始碼。

在我帶你一步一步地查閱這個函式之前,我推薦你自己嘗試理解這個函式。這是一個很簡單的函式,儘管你不知道真正的細節,但大多數程式碼看起來都很清晰。

PHP函式的骨架

所有的PHP函式都使用同一個基本結構。在函式頂部定義了各個變數,然後呼叫zend_parse_parameters函式,然後到了主要的邏輯,當中有RETURN_***和php_error_docref的呼叫。

那麼,讓我們以函式的定義來開始:

zval *needle;

char *haystack;

char *found = NULL;

char needle_char[2];

long offset = 0;

int haystack_len;

第一行定義了一個指向zval的指標needle。zval是在PHP內部代表任意一個PHP變數的定義。它真正是怎麼樣的會在下一篇文章重點談論。

第二行定義了指向單個字元的指標haystack。這時候,你需要記住,在C語言裡面,陣列代表指向它們第一個元素的指標。比如說,haystack變數會指向你所傳遞的$haystack字串變數的第一個字元。haystack + 1會指向第二個字元,haystack + 2指向第三個,以此類推。因此,通過逐個遞增指標,可以讀取整個字串。

那麼問題來了,PHP需要知道字串在哪裡結束。不然的話,它會一直遞增指標而不會停止。為了解決這個問題,PHP也儲存了明確的長度,這就是haystack_len變數。

現在,在上面的定義中,我們感興趣的是offset變數,這個變數用來儲存函式的第三個引數:開始搜尋的偏移量。它使用long來定義,跟int一樣,也是整型資料型別。現在這兩者的差異並不重要,但你需要知道的是在PHP中,整型值使用long來儲存,字串的長度使用int來儲存。

現在來看看下面的三行:

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sz|l", &haystack, &haystack_len, &needle, &offset) == FAILURE) {

return;

}

這三行程式碼做的事情就是,獲取傳遞到函式的引數,然後把它們儲存到上面宣告的變數中。

傳遞給函式的第一個引數是傳遞引數的數量。這個數字通過ZEND_NUM_ARGS()巨集提供。

下一個函式是TSRMLS_CC巨集,這是PHP的一種特性。你會發現這個奇怪的巨集分散在PHP程式碼庫的很多地方。是執行緒安全資源管理器(TSRM)的一部分,它保證PHP不會在多執行緒之間混亂變數。這對我們來說不是很重要,當你在程式碼中看到TSRMLS_CC(或者TSRMLS_DC)的時候,忽略它就行。(有一個奇怪的地方你需要注意的是,在”argument”之前沒有逗號。這是因為不管你是否使用執行緒安全建立函式,該巨集會被解釋為空或者, trsm_ls。因此,逗號是巨集的一部分。)

現在,我們來到重要的東西:”sz|l”字串標記了函式接收的引數。:

s // 第一個引數是字串

z // 第二個引數是一個zval結構體,任意的變數

| // 標識接下來的引數是可選的

l // 第三個引數是long型別(整型)

除了s,z,l之外,還有更多的標識型別,但是大部分都能從字元中清楚其意思。例如b是boolean,d是double(浮點型數字),a是array,f是回撥(function),o是object。

接下來的引數&haystack,&haystack_len,&needle,&offset指定了需要賦值的引數的變數。你可以看到,它們都是使用引用(&)傳遞的,意味著它們傳遞的不是變數本身,而是指向它們的指標。

這個函式呼叫之後,haystack會包含haystack字串,haystack_len是字串的長度,needle是needle的值,offset是開始的偏移量。

而且,這個函式使用FAILURE(當你嘗試傳遞無效引數到函式時會發生,比如傳遞一個數組賦值到字串)來檢查。這種情況下zend_parse_parameters函式會丟擲警告,而此函式馬上返回(會返回null給PHP的使用者層程式碼)。

在引數解析完畢以後,主函式體開始:

if (offset < 0 || offset > haystack_len) {

php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string");

RETURN_FALSE;

}

這段程式碼做的事情很明顯,如果offset超出了邊界,一個E_WARNING級別的錯誤會通過php_error_docref函式丟擲,然後函式使用RETURN_FALSE巨集返回false。

php_error_docref是一個錯誤函式,你可以在擴充套件目錄找到它(比如,ext資料夾)。它的名字根據它在錯誤頁面中返回文件參考(就是那些不會正常工作的函式)定義。還有一個zend_error函式,它主要被Zend Engine使用,但也經常出現在擴充套件程式碼中。

兩個函式都使用sprintf函式,比如格式化資訊,因此錯誤資訊可以包含佔位符,那些佔位符會被後面的引數填充。下面有一個例子:

php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write %d bytes to %s", Z_STRLEN_PP(tmp), filename);

// %d is filled with Z_STRLEN_PP(tmp)

// %s is filled with filename

讓我們繼續解析程式碼:

if (Z_TYPE_P(needle) == IS_STRING) {

if (!Z_STRLEN_P(needle)) {

php_error_docref(NULL TSRMLS_CC, E_WARNING, "Empty delimiter");

RETURN_FALSE;

}

found = php_memnstr(haystack + offset,

Z_STRVAL_P(needle),

Z_STRLEN_P(needle),

haystack + haystack_len);

}

前面的5行非常清晰:這個分支只會在needle為字串的.情況下執行,而且如果它是空的話會丟擲錯誤。然後到了比較有趣的一部分:php_memnstr被呼叫了,這個函式做了主要的工作。跟往常一樣,你可以點選該函式名然後檢視它的原始碼。

php_memnstr返回指向needle在haystack第一次出現的位置的指標(這就是為什麼found變數要定義為char *,例如,指向字元的指標)。從這裡可以知道,偏移量(offset)可以通過減法被簡單地計算,可以在函式的最後看到:

RETURN_LONG(found - haystack);

最後,讓我們來看看當needle作為非字串的時候的分支:

else {

if (php_needle_needle, needle_char TSRMLS_CC) != SUCCESS) {

RETURN_FALSE;

}

needle_char[1] = 0;

found = php_memnstr(haystack + offset,

needle_char,

1,

haystack + haystack_len);

}

我只引用在手冊上寫的”如果 needle 不是一個字串,那麼它將被轉換為整型並被視為字元順序值。”這基本上說明,除了寫strpos($str, 'A'),你還可以寫strpos($str, 65),因為A字元的編碼是65。

如果你再檢視變數定義,你可以看到needle_char被定義為char needle_char[2],即有兩個字元的字串,php_needle_char會將真正的字元(在這裡是’A’)到needle_char[0]。然後strpos函式會設定needle_char[1]為0。這背後的原因是因為,在C裡面,字串是使用’’結尾,就是說,最後一個字元被設定為NUL(編碼為0的字元)。在PHP的語法環境裡,這樣的情況不存在,因為PHP儲存了所有字串的長度(因此它不需要0來幫助找到字串的結尾),但是為了保證與C函式的相容性,還是在PHP的內部實現了。

Zend functions

我對strpos這個函式感覺好累,讓我們找另一個函式吧:strlen。我們使用之前的方法:

從PHP5.4原始碼根目錄開始搜尋strlen。

你會看到一堆無關的函式的使用,因此,搜尋“PHP_FUNCTION strlen”。當你這麼搜尋的時候,你會發現一些奇怪的事情發生了:沒有任何的結果。

原因是,strlen是少數通過Zend Engine而不是PHP擴充套件定義的函式。這種情況下,函式不是使用PHP_FUNCTION(strlen)定義,而是ZEND_FUNCTION(strlen)。因此,我們也要搜尋“ZEND_FUNCTION strlen”。

我們都知道,我們需要點選沒有分號結尾的連結跳到原始碼的定義。這個連結帶我們到下面的函式定義:

ZEND_FUNCTION(strlen)

{

char *s1;

int s1_len;

if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &s1, &s1_len) == FAILURE) {

return;

}

RETVAL_LONG(s1_len);

}

這個函式實現太簡單了,我不覺得我還需要進一步的解釋。

方法

我們會談論類和物件如何工作的更多細節在其他文章裡,但作為一個小小的劇透:你可以通過在搜尋框搜尋ClassName::methodName來搜尋物件方法。例如,嘗試搜尋SplFixedArray::getSize。

下一部分

下一部分會再次發表在。會談論到zval是什麼,它們是怎麼工作的,以及它們是怎麼在原始碼中被使用的(所有的Z_***巨集)。