摘要:文章來自原文歡迎來到給開發者的源碼系列的第二部分。是在內部代表任意一個變量的定義。這種情況下函數會拋出警告,而此函數馬上返回會返回給的用戶層代碼。原因是,是少數通過而不是擴展定義的函數。下一部分下一部分會再次發表在。
文章來自: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開發者的PHP源碼"系列的第二部分。
在上一篇中,ircmaxell說明了你可以在哪里找到PHP的源碼,它的基本目錄結構以及簡單地介紹了一些C語言(因為PHP是用C語言來寫的)。如果你錯過了那篇文章,在你開始讀這篇文章之前也許你應該讀一下它。
在這篇文章中,我們談論的是定位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_char(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里面,字符串是使用"0"結尾,就是說,最后一個字符被設置為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_*宏)。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/21344.html
摘要:為了防止你錯過了之前的文章,以下是鏈接第一部分給開發者的源碼源碼結構第二部分理解內部函數的定義第三部分的變量實現所有的東西都是哈希表基本上,里面的所有東西都是哈希表。哈希后的結果可以被作為正常的數組的鍵值又名為內存塊。表示哈希表的容量。 文章來自:http://www.hoohack.me/2016/02/15/understanding-phps-internal-array-im...
摘要:另一個說明我叫它做宏。你可以為函數定義寫一個宏事實上,就是這么做的,但我們會在后面的文章中深入了解這個。我想說的是,宏允許在預處理編譯時使用更簡單的代碼。或者說頭文件定義了在文件中可以被其他文件看到的函數,包括預處理宏。 文章來自:http://www.hoohack.me/2016/02/04/phps-source-code-for-php-developers-ch 原文:ht...
摘要:注意這里提及到的引用計數指的不是代碼中的引用使用,而是變量的使用次數。之前當在一個新的地方使用時會復制一份并增加一次引用計數。屬于深度復制,比如在復制數組時,不僅僅是簡單增加數組的引用計數,而是制造一份全新值一樣的數組。 本文第一部分和第二均翻譯自Nikita Popov(nikic,PHP 官方開發組成員,柏林科技大學的學生) 的博客。為了更符合漢語的閱讀習慣,文中并不會逐字逐句的翻...
摘要:文章來自原文在給開發者的源碼系列的第三篇文章,我們打算擴展上一篇文章來幫助理解內部是怎么工作的。進入在的核心代碼中,變量被稱為。要轉換一個為值,就調用函數。有了這個東西,我們可以看到函數馬上調用函數。 文章來自:http://www.hoohack.me/2016/02/12/phps-source-code-for-php-developers-part3-variables-ch...
閱讀 2723·2023-04-25 22:15
閱讀 1804·2021-11-19 09:40
閱讀 2149·2021-09-30 09:48
閱讀 3212·2021-09-03 10:36
閱讀 2026·2021-08-30 09:48
閱讀 1854·2021-08-24 10:00
閱讀 2725·2019-08-30 15:54
閱讀 699·2019-08-30 15:54