成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

[譯] 理解 PHP 內(nèi)部函數(shù)的定義(給PHP開發(fā)者的PHP源碼-第二部分)

hizengzeng / 2468人閱讀

摘要:文章來自原文歡迎來到給開發(fā)者的源碼系列的第二部分。是在內(nèi)部代表任意一個(gè)變量的定義。這種情況下函數(shù)會(huì)拋出警告,而此函數(shù)馬上返回會(huì)返回給的用戶層代碼。原因是,是少數(shù)通過而不是擴(kuò)展定義的函數(shù)。下一部分下一部分會(huì)再次發(fā)表在。

文章來自:http://www.hoohack.me/2016/02/10/understanding-phps-internal-function-definitions-ch

原文:https://nikic.github.io/2012/03/16/Understanding-PHPs-internal-function-definitions.html

歡迎來到"給PHP開發(fā)者的PHP源碼"系列的第二部分。

在上一篇中,ircmaxell說明了你可以在哪里找到PHP的源碼,它的基本目錄結(jié)構(gòu)以及簡(jiǎn)單地介紹了一些C語(yǔ)言(因?yàn)镻HP是用C語(yǔ)言來寫的)。如果你錯(cuò)過了那篇文章,在你開始讀這篇文章之前也許你應(yīng)該讀一下它。

在這篇文章中,我們談?wù)摰氖嵌ㄎ籔HP內(nèi)部函數(shù)的定義,以及理解它們的原理。

如何找到函數(shù)的定義

作為開始,讓我們嘗試找出strpos函數(shù)的定義。

嘗試的第一步,就是去PHP 5.4根目錄然后在頁(yè)面頂部的搜索框輸入strpos。搜索的結(jié)果是一個(gè)很大的列表,展示了strpos在PHP源碼中出現(xiàn)的位置。

因?yàn)檫@個(gè)結(jié)果對(duì)我們并沒有太大的幫助,我們使用一個(gè)小技巧:我們搜索"PHP_FUNCTION strpos"(不要漏了雙引號(hào),它們很重要),而不是strpos.

現(xiàn)在我們得到兩個(gè)入口鏈接:

/PHP_5_4/ext/standard/
    php_string.h 48   PHP_FUNCTION(strpos);
    string.c     1789 PHP_FUNCTION(strpos)

第一個(gè)要注意的事情是,兩個(gè)位置都是在ext/standard文件夾。這就是我們希望找到的,因?yàn)閟trpos函數(shù)(跟大部分string,array和文件函數(shù)一樣)是standard擴(kuò)展的一部分。

現(xiàn)在,在新標(biāo)簽頁(yè)打開兩個(gè)鏈接,然后看看它們背后藏了什么代碼。

你會(huì)看到第一個(gè)鏈接帶你到了php_string.h文件,它包含了下面的代碼:

// ...
PHP_FUNCTION(strpos);
PHP_FUNCTION(stripos);
PHP_FUNCTION(strrpos);
PHP_FUNCTION(strripos);
PHP_FUNCTION(strrchr);
PHP_FUNCTION(substr);
// ...

這就是一個(gè)典型的頭文件(以.h后綴結(jié)尾的文件)的樣子:?jiǎn)渭兊暮瘮?shù)列表,函數(shù)在其他地方定義。事實(shí)上,我們對(duì)這些并不感興趣,因?yàn)槲覀円呀?jīng)知道我們要找的是什么。

第二個(gè)鏈接更有趣:它帶我們到string.c文件,這個(gè)文件包含了函數(shù)真正的源代碼。

在我?guī)阋徊揭徊降夭殚嗊@個(gè)函數(shù)之前,我推薦你自己嘗試?yán)斫膺@個(gè)函數(shù)。這是一個(gè)很簡(jiǎn)單的函數(shù),盡管你不知道真正的細(xì)節(jié),但大多數(shù)代碼看起來都很清晰。

PHP函數(shù)的骨架

所有的PHP函數(shù)都使用同一個(gè)基本結(jié)構(gòu)。在函數(shù)頂部定義了各個(gè)變量,然后調(diào)用zend_parse_parameters函數(shù),然后到了主要的邏輯,當(dāng)中有RETURN_***php_error_docref的調(diào)用。

那么,讓我們以函數(shù)的定義來開始:

zval *needle;
char *haystack;
char *found = NULL;
char  needle_char[2];
long  offset = 0;
int   haystack_len;

第一行定義了一個(gè)指向zval的指針needle。zval是在PHP內(nèi)部代表任意一個(gè)PHP變量的定義。它真正是怎么樣的會(huì)在下一篇文章重點(diǎn)談?wù)摗?/p>

第二行定義了指向單個(gè)字符的指針haystack。這時(shí)候,你需要記住,在C語(yǔ)言里面,數(shù)組代表指向它們第一個(gè)元素的指針。比如說,haystack變量會(huì)指向你所傳遞的$haystack字符串變量的第一個(gè)字符。haystack + 1會(huì)指向第二個(gè)字符,haystack + 2指向第三個(gè),以此類推。因此,通過逐個(gè)遞增指針,可以讀取整個(gè)字符串。

那么問題來了,PHP需要知道字符串在哪里結(jié)束。不然的話,它會(huì)一直遞增指針而不會(huì)停止。為了解決這個(gè)問題,PHP也保存了明確的長(zhǎng)度,這就是haystack_len變量。

現(xiàn)在,在上面的定義中,我們感興趣的是offset變量,這個(gè)變量用來保存函數(shù)的第三個(gè)參數(shù):開始搜索的偏移量。它使用long來定義,跟int一樣,也是整型數(shù)據(jù)類型?,F(xiàn)在這兩者的差異并不重要,但你需要知道的是在PHP中,整型值使用long來存儲(chǔ),字符串的長(zhǎng)度使用int來存儲(chǔ)。

現(xiàn)在來看看下面的三行:

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

這三行代碼做的事情就是,獲取傳遞到函數(shù)的參數(shù),然后把它們存儲(chǔ)到上面聲明的變量中。

傳遞給函數(shù)的第一個(gè)參數(shù)是傳遞參數(shù)的數(shù)量。這個(gè)數(shù)字通過ZEND_NUM_ARGS()宏提供。

下一個(gè)函數(shù)是TSRMLS_CC宏,這是PHP的一種特性。你會(huì)發(fā)現(xiàn)這個(gè)奇怪的宏分散在PHP代碼庫(kù)的很多地方。是線程安全資源管理器(TSRM)的一部分,它保證PHP不會(huì)在多線程之間混亂變量。這對(duì)我們來說不是很重要,當(dāng)你在代碼中看到TSRMLS_CC(或者TSRMLS_DC)的時(shí)候,忽略它就行。(有一個(gè)奇怪的地方你需要注意的是,在"argument"之前沒有逗號(hào)。這是因?yàn)椴还苣闶欠袷褂镁€程安全創(chuàng)建函數(shù),該宏會(huì)被解釋為空或者, trsm_ls。因此,逗號(hào)是宏的一部分。)

現(xiàn)在,我們來到重要的東西:"sz|l"字符串標(biāo)記了函數(shù)接收的參數(shù)。:

s  // 第一個(gè)參數(shù)是字符串
z  // 第二個(gè)參數(shù)是一個(gè)zval結(jié)構(gòu)體,任意的變量
|  // 標(biāo)識(shí)接下來的參數(shù)是可選的
l  // 第三個(gè)參數(shù)是long類型(整型)

除了s,z,l之外,還有更多的標(biāo)識(shí)類型,但是大部分都能從字符中清楚其意思。例如b是boolean,d是double(浮點(diǎn)型數(shù)字),a是array,f是回調(diào)(function),o是object。

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

這個(gè)函數(shù)調(diào)用之后,haystack會(huì)包含haystack字符串,haystack_len是字符串的長(zhǎng)度,needle是needle的值,offset是開始的偏移量。

而且,這個(gè)函數(shù)使用FAILURE(當(dāng)你嘗試傳遞無效參數(shù)到函數(shù)時(shí)會(huì)發(fā)生,比如傳遞一個(gè)數(shù)組賦值到字符串)來檢查。這種情況下zend_parse_parameters函數(shù)會(huì)拋出警告,而此函數(shù)馬上返回(會(huì)返回null給PHP的用戶層代碼)。

在參數(shù)解析完畢以后,主函數(shù)體開始:

if (offset < 0 || offset > haystack_len) {
    php_error_docref(NULL TSRMLS_CC, E_WARNING, "Offset not contained in string");
    RETURN_FALSE;
}

這段代碼做的事情很明顯,如果offset超出了邊界,一個(gè)E_WARNING級(jí)別的錯(cuò)誤會(huì)通過php_error_docref函數(shù)拋出,然后函數(shù)使用RETURN_FALSE宏返回false。

php_error_docref是一個(gè)錯(cuò)誤函數(shù),你可以在擴(kuò)展目錄找到它(比如,ext文件夾)。它的名字根據(jù)它在錯(cuò)誤頁(yè)面中返回文檔參考(就是那些不會(huì)正常工作的函數(shù))定義。還有一個(gè)zend_error函數(shù),它主要被Zend Engine使用,但也經(jīng)常出現(xiàn)在擴(kuò)展代碼中。

兩個(gè)函數(shù)都使用sprintf函數(shù),比如格式化信息,因此錯(cuò)誤信息可以包含占位符,那些占位符會(huì)被后面的參數(shù)填充。下面有一個(gè)例子:

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

讓我們繼續(xù)解析代碼:

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行非常清晰:這個(gè)分支只會(huì)在needle為字符串的情況下執(zhí)行,而且如果它是空的話會(huì)拋出錯(cuò)誤。然后到了比較有趣的一部分:php_memnstr被調(diào)用了,這個(gè)函數(shù)做了主要的工作。跟往常一樣,你可以點(diǎn)擊該函數(shù)名然后查看它的源碼。

php_memnstr返回指向needle在haystack第一次出現(xiàn)的位置的指針(這就是為什么found變量要定義為char *,例如,指向字符的指針)。從這里可以知道,偏移量(offset)可以通過減法被簡(jiǎn)單地計(jì)算,可以在函數(shù)的最后看到:

RETURN_LONG(found - haystack);

最后,讓我們來看看當(dāng)needle作為非字符串的時(shí)候的分支:

else {
    if (php_needle_char(needle, needle_char TSRMLS_CC) != SUCCESS) {
        RETURN_FALSE;
    }
    needle_char[1] = 0;

    found = php_memnstr(haystack + offset,
                        needle_char,
                        1,
                        haystack + haystack_len);
}

我只引用在手冊(cè)上寫的"如果 needle 不是一個(gè)字符串,那么它將被轉(zhuǎn)換為整型并被視為字符順序值。"這基本上說明,除了寫strpos($str, "A"),你還可以寫strpos($str, 65),因?yàn)锳字符的編碼是65。

如果你再查看變量定義,你可以看到needle_char被定義為char needle_char[2],即有兩個(gè)字符的字符串,php_needle_char會(huì)將真正的字符(在這里是"A")到needle_char[0]。然后strpos函數(shù)會(huì)設(shè)置needle_char[1]為0。這背后的原因是因?yàn)?,在C里面,字符串是使用"0"結(jié)尾,就是說,最后一個(gè)字符被設(shè)置為NUL(編碼為0的字符)。在PHP的語(yǔ)法環(huán)境里,這樣的情況不存在,因?yàn)镻HP存儲(chǔ)了所有字符串的長(zhǎng)度(因此它不需要0來幫助找到字符串的結(jié)尾),但是為了保證與C函數(shù)的兼容性,還是在PHP的內(nèi)部實(shí)現(xiàn)了。

Zend functions

我對(duì)strpos這個(gè)函數(shù)感覺好累,讓我們找另一個(gè)函數(shù)吧:strlen。我們使用之前的方法:

從PHP5.4源碼根目錄開始搜索strlen。

你會(huì)看到一堆無關(guān)的函數(shù)的使用,因此,搜索“PHP_FUNCTION strlen”。當(dāng)你這么搜索的時(shí)候,你會(huì)發(fā)現(xiàn)一些奇怪的事情發(fā)生了:沒有任何的結(jié)果。

原因是,strlen是少數(shù)通過Zend Engine而不是PHP擴(kuò)展定義的函數(shù)。這種情況下,函數(shù)不是使用PHP_FUNCTION(strlen)定義,而是ZEND_FUNCTION(strlen)。因此,我們也要搜索“ZEND_FUNCTION strlen”。

我們都知道,我們需要點(diǎn)擊沒有分號(hào)結(jié)尾的鏈接跳到源碼的定義。這個(gè)鏈接帶我們到下面的函數(shù)定義:

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);
}

這個(gè)函數(shù)實(shí)現(xiàn)太簡(jiǎn)單了,我不覺得我還需要進(jìn)一步的解釋。

方法

我們會(huì)談?wù)擃惡蛯?duì)象如何工作的更多細(xì)節(jié)在其他文章里,但作為一個(gè)小小的劇透:你可以通過在搜索框搜索ClassName::methodName來搜索對(duì)象方法。例如,嘗試搜索SplFixedArray::getSize。

下一部分

下一部分會(huì)再次發(fā)表在。會(huì)談?wù)摰絲val是什么,它們是怎么工作的,以及它們是怎么在源碼中被使用的(所有的Z_*宏)。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/21344.html

相關(guān)文章

  • [] 理解數(shù)組在 PHP 內(nèi)部實(shí)現(xiàn)(PHP發(fā)者PHP源碼-四部

    摘要:為了防止你錯(cuò)過了之前的文章,以下是鏈接第一部分給開發(fā)者的源碼源碼結(jié)構(gòu)第二部分理解內(nèi)部函數(shù)的定義第三部分的變量實(shí)現(xiàn)所有的東西都是哈希表基本上,里面的所有東西都是哈希表。哈希后的結(jié)果可以被作為正常的數(shù)組的鍵值又名為內(nèi)存塊。表示哈希表的容量。 文章來自:http://www.hoohack.me/2016/02/15/understanding-phps-internal-array-im...

    solocoder 評(píng)論0 收藏0
  • PHP源碼研究

    摘要:最近閑來無事,所以對(duì)這門語(yǔ)言進(jìn)行更深一層的了解,對(duì)源碼進(jìn)行一番研究,是如何執(zhí)行我們寫的腳本的。引擎是語(yǔ)言實(shí)現(xiàn)的最為重要的部分,是最基礎(chǔ)最核心的部分,它的源碼在目錄下,代碼從編譯到執(zhí)行都是由完成的,后面章節(jié)絕大部分的源碼分析都是針對(duì)的。 最近閑來無事,所以對(duì)PHP這門語(yǔ)言進(jìn)行更深一層的了解,對(duì)源碼進(jìn)行一番研究,是如何執(zhí)行我們寫的PHP腳本的。 1.1.3 PHP的相關(guān)組成 1.1.3.1...

    Developer 評(píng)論0 收藏0
  • [] PHP發(fā)者PHP源碼-一部-源碼結(jié)構(gòu)

    摘要:另一個(gè)說明我叫它做宏。你可以為函數(shù)定義寫一個(gè)宏事實(shí)上,就是這么做的,但我們會(huì)在后面的文章中深入了解這個(gè)。我想說的是,宏允許在預(yù)處理編譯時(shí)使用更簡(jiǎn)單的代碼?;蛘哒f頭文件定義了在文件中可以被其他文件看到的函數(shù),包括預(yù)處理宏。 文章來自:http://www.hoohack.me/2016/02/04/phps-source-code-for-php-developers-ch 原文:ht...

    wenzi 評(píng)論0 收藏0
  • []變量在 PHP7 內(nèi)部實(shí)現(xiàn)(一)

    摘要:注意這里提及到的引用計(jì)數(shù)指的不是代碼中的引用使用,而是變量的使用次數(shù)。之前當(dāng)在一個(gè)新的地方使用時(shí)會(huì)復(fù)制一份并增加一次引用計(jì)數(shù)。屬于深度復(fù)制,比如在復(fù)制數(shù)組時(shí),不僅僅是簡(jiǎn)單增加數(shù)組的引用計(jì)數(shù),而是制造一份全新值一樣的數(shù)組。 本文第一部分和第二均翻譯自Nikita Popov(nikic,PHP 官方開發(fā)組成員,柏林科技大學(xué)的學(xué)生) 的博客。為了更符合漢語(yǔ)的閱讀習(xí)慣,文中并不會(huì)逐字逐句的翻...

    codergarden 評(píng)論0 收藏0
  • [] PHP 變量實(shí)現(xiàn)(PHP發(fā)者PHP源碼-三部

    摘要:文章來自原文在給開發(fā)者的源碼系列的第三篇文章,我們打算擴(kuò)展上一篇文章來幫助理解內(nèi)部是怎么工作的。進(jìn)入在的核心代碼中,變量被稱為。要轉(zhuǎn)換一個(gè)為值,就調(diào)用函數(shù)。有了這個(gè)東西,我們可以看到函數(shù)馬上調(diào)用函數(shù)。 文章來自:http://www.hoohack.me/2016/02/12/phps-source-code-for-php-developers-part3-variables-ch...

    Imfan 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<