摘要:文章來自原文在給開發(fā)者的源碼系列的第三篇文章,我們打算擴(kuò)展上一篇文章來幫助理解內(nèi)部是怎么工作的。進(jìn)入在的核心代碼中,變量被稱為。要轉(zhuǎn)換一個為值,就調(diào)用函數(shù)。有了這個東西,我們可以看到函數(shù)馬上調(diào)用函數(shù)。
文章來自:http://www.hoohack.me/2016/02/12/phps-source-code-for-php-developers-part3-variables-ch
原文:http://blog.ircmaxell.com/2012/03/phps-source-code-for-php-developers_21.html
在"給PHP開發(fā)者的PHP源碼"系列的第三篇文章,我們打算擴(kuò)展上一篇文章來幫助理解PHP內(nèi)部是怎么工作的。在第一篇文章,我們介紹了如何查看PHP的源碼,它的代碼結(jié)構(gòu)是怎樣的以及一些介紹給PHP開發(fā)者的C指針基礎(chǔ)。第二篇文章介紹了函數(shù)。這一次,我們打算深入PHP最有用的結(jié)構(gòu)之一:變量。
進(jìn)入ZVAL在PHP的核心代碼中,變量被稱為ZVAL。這個結(jié)構(gòu)之所以那么重要是有原因的,不僅僅是因為PHP使用弱類型而C使用強(qiáng)類型。那么ZVAL是怎么解決這個問題的呢?要回答這個問題,我們需要認(rèn)真的查看ZVAL類型的定義。要查看這個定義,讓我們嘗試在lxr頁面的定義搜索框里搜索zval。乍一眼看去,我們似乎找不到任何有用的東西。但是有一行typedef在zend.h文件(typedef在C里面是一種定義新的數(shù)據(jù)類型的方式)。這個也許就是我們要找的東西,再繼續(xù)查看。原來,這看起來是不相干的。這里并沒有任何有用的東西。但為了確認(rèn)一些,我們來點擊_zval_struct這一行。
struct _zval_struct { /* Variable information */ zvalue_value value; /* value */ zend_uint refcount__gc; zend_uchar type; /* active type */ zend_uchar is_ref__gc; };
然后我們就得到PHP的基礎(chǔ),zval。看起來很簡單,對嗎?是的,沒錯,但這里還有一些很有意義的神奇的東西。注意,這是一個結(jié)構(gòu)或結(jié)構(gòu)體。基本上,這可以看作PHP里面的類,這些類只有公共的屬性。這里,我們有四個屬性:value,refcount__gc,type以及is_ref__gc。讓我們來一一查看這些屬性(省略它們的順序)。
Value我們第一個談?wù)摰脑厥莢alue變量,它的類型是zvalue_value。我不認(rèn)識你,但我也從來沒有聽說過zvalue_value。那么讓我們嘗試弄懂它是什么。跟網(wǎng)站的其他部分一樣,你可以點擊某個類型查看它的定義。一旦你點擊了,你會看到它的定義跟下面的是一樣的:
typedef union _zvalue_value { long lval; /* long value */ double dval; /* double value */ struct { char *val; int len; } str; HashTable *ht; /* hash table value */ zend_object_value obj; } zvalue_value;
現(xiàn)在,這里有一些黑科技。看到那個union的定義嗎?那意味著這不是真正的結(jié)構(gòu)體,而是一個多帶帶的類型。但是有多個類型的變量在里面。如果這里面有多種類型的話,那它怎么能作為單一的類型呢?我很高興你問了這個問題。要理解這個問題,我們需要先回想我們在第一篇文章談?wù)摰腃語言中的類型。
在C里面,變量只是一行內(nèi)存地址的標(biāo)簽。也可以說類型只是標(biāo)識哪一塊內(nèi)存將被使用的方式。在C里面沒有使用任何東西將4個字節(jié)的字符串和整型值分隔開。它們都只是一整塊的內(nèi)存。編譯器會嘗試通過"標(biāo)識"內(nèi)存段作為變量來解析它,然后將這些變量轉(zhuǎn)換為特定的類型,但這并不是總是成功(順便說一句,當(dāng)一個變量“重寫”它得到的內(nèi)存段,那將會產(chǎn)生段錯誤)。
那么,據(jù)我們所知,union是多帶帶的類型,它根據(jù)怎么被訪問而使用不同的方式解釋。這可以讓我們定義一個值來支持多種類型。有一點要注意的是,所有類型的數(shù)據(jù)都必須使用同一塊內(nèi)存來存儲。這個例子,在64位的編譯器,long和double都會占用64個位來保存。字符串結(jié)構(gòu)體會占用96位(64位存儲字符指針,32位保存整型長度)。hash_table會占用64位,還有zend_object_value會占用96位(32位用來存儲元素,剩下的64位來存儲指針)。而整一個union會占用最大元素的內(nèi)存大小,因此在這里就是96位。
現(xiàn)在,如果再看清楚這個聯(lián)合體(union),我們可以看到只有5種PHP數(shù)據(jù)類型在這里(long == int,double == float,str == string,hashtable == array,zend_object_value == object)。那么剩下的數(shù)據(jù)類型去了哪里呢?原來,這個結(jié)構(gòu)體已經(jīng)足夠來存儲剩余的數(shù)據(jù)類型。BOOL使用long(int)來存儲,NULL不占用數(shù)據(jù)段,RESOURCE也使用long來存儲。
TYPE因為這個value聯(lián)合體并沒有控制它是怎么被訪問的,我們需要其他方式來記錄變量的類型。這里,我們可以通過數(shù)據(jù)類型來得出如何訪問value的信息。它使用type這個字節(jié)來處理這個問題(zend_uchar是一個無符號的字符,或者內(nèi)存中的一個字節(jié))。它從zend類型常量保留這些信息。這真的是一種魔法,是需要使用zval.type = IS_LONG來定義整型數(shù)據(jù)。因此這個字段和value字段就足夠讓我們知道PHP變量的類型和值。
IS_REF這個字段標(biāo)識變量是否為引用。那就是說,如果你執(zhí)行了在變量里執(zhí)行了$foo = &$bar。如果它是0,那么變量就不是一個引用,如果它是1,那么變量就是一個引用。它并沒有做太多的事情。那么,在我們結(jié)束_zval_struct之前,再看一看它的第四個成員。
REFCOUNT這個變量是指向PHP變量容器的指針的計數(shù)器。也就是說,如果refcount是1,那就表示有一個PHP變量使用這個容器。如果refcount是2,那就表示有兩個PHP變量指向同一個變量容器。多帶帶的refcount變量并沒有太多有用的信息,但如果它與is_ref一起使用,就構(gòu)成了垃圾回收器和寫時復(fù)制的基礎(chǔ)。它允許我們使用同一個zval容器來保存一個或多個PHP變量。refcount的語義解釋超出這篇文章的范圍,如果你想繼續(xù)深入,我推薦你查看這篇文檔。
這就是ZVAL的所有內(nèi)容。
它是怎么工作的?在PHP內(nèi)部,zval使用跟其他C變量一樣,作為內(nèi)存段或者一個指向內(nèi)存段的指針(或者指向指針的指針,等等),傳遞到函數(shù)。一旦我們有了變量,我們就想訪問它里面的數(shù)據(jù)。那我們要怎么做到呢?我們使用定義在zend_operators.h文件里面的宏來跟zval一起使用,使得訪問數(shù)據(jù)更簡單。有一點很重要的是,每一個宏都有多個拷貝。不同的是它們的前綴。例如,要得出zval的類型,有Z_TYPE(zval)宏,這個宏返回一個整型數(shù)據(jù)來表示zval參數(shù)。但這里還有一個Z_TYPE(zval_p)宏,它跟Z_TYPE(zval)做的事情是一樣的,但它返回的是指向zval的指針。事實上,除了參數(shù)的屬性不一樣之外,這兩個函數(shù)是一樣的,實際上,我們可以使用Z_TYPE(*zval_p),但_P和_PP讓事情更簡單。
我們可以使用VAL這一類宏來獲取zval的值。可以調(diào)用Z_LVAL(zval)來得到整型值(比如整型數(shù)據(jù)和資源數(shù)據(jù))。調(diào)用Z_DVAL(zval)來得到浮點值。還有很多其他的,到這里到此為止。要注意的關(guān)鍵是,為了在C里面獲取zval的值,你需要使用宏(或應(yīng)該)。因此,當(dāng)我們看見有函數(shù)使用它們時,我們就知道它是從zval里面提取它的值。
那么,類型呢?到現(xiàn)在為止,我們知識談?wù)摿祟愋秃蛕val的值。我們都知道,PHP幫我們做了類型判斷。因此,如果我們喜歡,我們可以將一個字符串當(dāng)作一個整型值。我們把這一步叫做convert_to_type。要轉(zhuǎn)換一個zval為string值,就調(diào)用convert_to_string函數(shù)。它會改變我們傳遞給函數(shù)的ZVAL的類型。因此,如果你看到有函數(shù)在調(diào)用這些函數(shù),你就知道它是在轉(zhuǎn)換參數(shù)的數(shù)據(jù)類型。
Zend_Parse_Paramenters上一篇文章中,介紹了zend_parse_paramenters這個函數(shù)。既然我們知道PHP變量在C里面是怎么表示的,那我們就來深入看看。
ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, ...) { va_list va; int retval; RETURN_IF_ZERO_ARGS(num_args, type_spec, 0); va_start(va, type_spec); retval = zend_parse_va_args(num_args, type_spec, &va, 0 TSRMLS_CC); va_end(va); return retval; }
現(xiàn)在,從表面上看,這看起來很迷惑。重點要理解的是,va_list類型只是一個使用"..."的可變參數(shù)列表。因此,它跟PHP中的func_get_args()函數(shù)的構(gòu)造差不多。有了這個東西,我們可以看到zend_parse_parameters函數(shù)馬上調(diào)用zend_parse_va_args函數(shù)。我們繼續(xù)往下看看這個函數(shù)...
這個函數(shù)看起來很有趣。第一眼看去,它好像做了很多事情。但仔細(xì)看看。首先,我們可以看到一個for循環(huán)。這個for循環(huán)主要遍歷從zend_parse_parameters傳遞過來的type_spec字符串。在循環(huán)里面我們可以看到它只是計算期望接收到的參數(shù)數(shù)量。它是如何做到這些的研究就留給讀者。
繼續(xù)往下看,我么可以看到有一些合理的檢查(檢查參數(shù)是否都正確地傳遞),還有錯誤檢查,檢查是否傳遞了足夠數(shù)量的參數(shù)。接下來進(jìn)入一個我們感興趣的循環(huán)。這個循環(huán)真正解析那些參數(shù)。在循環(huán)里面,我們可以看到有三個if語句。第一個處理可選參數(shù)的標(biāo)識符。第二個處理var-args(參數(shù)的數(shù)量)。第三個if語句正是我們感興趣的。可以看到,這里調(diào)用了zend_parse_arg()函數(shù)。讓我們再深入看看這個函數(shù)...
繼續(xù)往下看,我們可以看到這里有一些非常有趣的事情。這個函數(shù)再調(diào)用另一個函數(shù)(zend_parse_arg_impl),然后得到一些錯誤信息。這在PHP里面是一種很常見的模式,將函數(shù)的錯誤處理工作提取到父函數(shù)。這樣代碼實現(xiàn)和錯誤處理就分開了,而且可以最大化地重用。你可以繼續(xù)深入研究那個函數(shù),非常容易理解。但我們現(xiàn)在仔細(xì)看看zend_parse_arg_impl()...
現(xiàn)在,我們真正到了PHP內(nèi)部函數(shù)解析參數(shù)的步驟。讓我們看看第一個switch語句的分支,這個分支用來解析整型參數(shù)。接下來的應(yīng)該很容易理解。那么,我們從分支的第一行開始吧:
long *p = va_arg(*va, long *);
如果你記得我們之前說的,va_args是C語言處理變量參數(shù)的方式。所以這里是定義一個整型指針(long在C里面是整型)。總之,它從va_arg函數(shù)里面得到指針。這說明,它得到傳遞給zend_parse_parameters函數(shù)的參數(shù)的指針。所以這就是我們會用分支結(jié)束后的值賦值的指針結(jié)果。接下來,我們可以看到進(jìn)入一個根據(jù)傳遞進(jìn)來的變量(zval)類型的分支。我們先看看IS_STRING分支(這一步會在傳遞整型值到字符串變量時執(zhí)行)。
case IS_STRING: { double d; int type; if ((type = is_numeric_string(Z_STRVAL_PP(arg), Z_STRLEN_PP(arg), p, &d, -1)) == 0) { return "long"; } else if (type == IS_DOUBLE) { if (c == "L") { if (d > LONG_MAX) { *p = LONG_MAX; break; } else if (d < LONG_MIN) { *p = LONG_MIN; break; } } *p = zend_dval_to_lval(d); } } break;
現(xiàn)在,這個做的事情并沒有看起來的那么多。所有的事情都?xì)w結(jié)與is_numeric_string函數(shù)。總的來說,該函數(shù)檢查字符串是否只包含整數(shù)字符,如果不是的話就返回0。如果是的話,它將該字符串解析到變量里(整型或浮點型,p或d),然后返回數(shù)據(jù)類型。所以我們可以看到,如果字符串不是純數(shù)字,他返回“l(fā)ong”字符串。這個字符串用來包裝錯誤處理函數(shù)。否則,如果字符串表示double(浮點型),它先檢查這個浮點數(shù)作為整型數(shù)來存儲的話是否太大,然后它使用zend_dval_to_lval函數(shù)來幫助解析浮點數(shù)到整型數(shù)。這就是我們所知道的。我們已經(jīng)解析了我們的字符串參數(shù)。現(xiàn)在繼續(xù)看看其他分支:
case IS_DOUBLE: if (c == "L") { if (Z_DVAL_PP(arg) > LONG_MAX) { *p = LONG_MAX; break; } else if (Z_DVAL_PP(arg) < LONG_MIN) { *p = LONG_MIN; break; } } case IS_NULL: case IS_LONG: case IS_BOOL: convert_to_long_ex(arg); *p = Z_LVAL_PP(arg); break;
這里,我們可以看到解析浮點數(shù)的操作,這一步跟解析字符串里的浮點數(shù)相似(巧合?)。有一個很重要的事情要注意的是,如果參數(shù)的標(biāo)識不是大寫"L",它會跟其他類型變量一樣的處理方式(這個case語句沒有break)。現(xiàn)在,我們還有一個有趣的函數(shù),convert_to_long_ex()。這跟我們之前說到的convert_to_type()函數(shù)集合是一類的,該函數(shù)轉(zhuǎn)換參數(shù)為特定的類型。唯一的不同是,如果參數(shù)不是引用的話(因為這個函數(shù)在改變數(shù)據(jù)類型),這個函數(shù)就將變量的值及其引用分離(拷貝)了。( The only difference is that it separates (copies) the passed in variable if it"s not a reference (since it"s changing the type). )這就是寫時復(fù)制的作用。因此,當(dāng)我們傳遞一個浮點數(shù)到到一個非引用的整型變量,該函數(shù)會把它當(dāng)作整型來處理,但我們?nèi)匀豢梢缘玫礁↑c型數(shù)據(jù)。
case IS_ARRAY: case IS_OBJECT: case IS_RESOURCE: default: return "long";
最后,我們還有另外三個case分支。我們可以看到,如果你傳遞一個數(shù)組、對象、資源或者其他不知道的類型到整型變量中,你會得到錯誤。
剩下的部分我們留給讀者。閱讀zend_parse_arg_impl函數(shù)對更好地理解額PHP類型判斷系統(tǒng)真的很有用。一部分一部分地讀,然后盡量追蹤在C里面的各種參數(shù)的狀態(tài)和類型。
下一部分下一部分會在Nikic的博客(我們會在這個系列的文章來回跳轉(zhuǎn))。在下一篇,他會談到數(shù)組的所有內(nèi)容。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/21342.html
摘要:為了防止你錯過了之前的文章,以下是鏈接第一部分給開發(fā)者的源碼源碼結(jié)構(gòu)第二部分理解內(nèi)部函數(shù)的定義第三部分的變量實現(xiàn)所有的東西都是哈希表基本上,里面的所有東西都是哈希表。哈希后的結(jié)果可以被作為正常的數(shù)組的鍵值又名為內(nèi)存塊。表示哈希表的容量。 文章來自:http://www.hoohack.me/2016/02/15/understanding-phps-internal-array-im...
摘要:另一個說明我叫它做宏。你可以為函數(shù)定義寫一個宏事實上,就是這么做的,但我們會在后面的文章中深入了解這個。我想說的是,宏允許在預(yù)處理編譯時使用更簡單的代碼。或者說頭文件定義了在文件中可以被其他文件看到的函數(shù),包括預(yù)處理宏。 文章來自:http://www.hoohack.me/2016/02/04/phps-source-code-for-php-developers-ch 原文:ht...
摘要:文章來自原文歡迎來到給開發(fā)者的源碼系列的第二部分。是在內(nèi)部代表任意一個變量的定義。這種情況下函數(shù)會拋出警告,而此函數(shù)馬上返回會返回給的用戶層代碼。原因是,是少數(shù)通過而不是擴(kuò)展定義的函數(shù)。下一部分下一部分會再次發(fā)表在。 文章來自:http://www.hoohack.me/2016/02/10/understanding-phps-internal-function-definitio...
摘要:獨立的擴(kuò)展可以獨立于源碼之外進(jìn)行分發(fā)。定義一個新擴(kuò)展我們給示例擴(kuò)展命名為。對于一個獨立擴(kuò)展來說,你只需要做一些宏調(diào)用即可。通過以上的步驟,你已經(jīng)有了一個獨立的擴(kuò)展了。 本文翻譯自 PHP 源碼中的 README.SELF-CONTAINED-EXTENSIONS。文中標(biāo)記了 注 的內(nèi)容均為自己添加。內(nèi)容有點老,也挺啰嗦,沒講什么深入的內(nèi)容,但是可以作為入門學(xué)習(xí)參考。 獨立的 PHP 擴(kuò)...
摘要:原文地址通過加載環(huán)境變量并且能夠自動的通過和自動調(diào)用這是一個版本為什么是你不能在代碼中存儲任何的敏感賬號數(shù)據(jù)存儲在環(huán)境中存儲配置是的一項規(guī)則在部署中可能變化的所有的內(nèi)容諸如數(shù)據(jù)庫認(rèn)證或者第三方服務(wù)的認(rèn)證應(yīng)該從代碼中剝離出來也就是環(huán)境變量的 原文地址:PHP dotenv 通過 .env 加載環(huán)境變量并且能夠自動的通過 getenv(), $_ENV 和 $_SERVER 自動調(diào)用. 這...
閱讀 2390·2021-11-15 11:37
閱讀 2639·2021-09-23 11:21
閱讀 2968·2021-09-07 10:11
閱讀 3176·2019-08-30 15:53
閱讀 2836·2019-08-29 15:13
閱讀 1619·2019-08-26 13:57
閱讀 1112·2019-08-26 12:23
閱讀 2453·2019-08-26 11:51