做者:王澍php
PHP中zval是全部變量的基礎。(zend_type.h 121行)ubuntu
其中zend_value存儲了具體數據,結構如圖: (zend_type.h 101行)api
整個zval結構體,佔用16字節,就支持了php全部類型。數組
PHP7中用如此簡單而巧妙的zval存儲了全部類型數據,那麼一個不肯定長度的字符串又如何能存儲在一個16字節的zval中呢?數據結構
<?php $a = "hello world"; echo $a;
經過GDB調試能夠看到:性能
type = 6,對照類型的定義,能夠看到類型是IS_STRING (zend_type.h 303行)編碼
因爲咱們的字符串長度不必定,因此單靠zval的16個字節是沒法直接存儲的,因而經過value中的str指向真正存儲字符串的內存地址。經過打印咱們能夠看到,這個地址的類型是zend_stringspa
先看一下它的數據結構,如圖 (zend_type.h 169行)3d
zend_string結構體中的gc
頭部先是gc,能夠看一下其餘複雜類型,頭部都有一個gc,它的做用是什麼?
看看gc的數據結構,如圖:指針
那麼做用就比較好猜想了,在程序執行gc或其餘操做的時候,對於任意一個複雜類型,指針頭部就是gc,裏面不光有引用計數,而且能經過u.v.type肯定該複雜類型的真正類型。
zend_string結構體中的h
從名字能夠猜想,這是字符串的hash,空間換時間的思想,把計算好的hash保存下來,提升性能。
zend_string中的len
比較明顯,它存儲了字符串的長度。
zend_string中的val[1]
這種寫法是c裏面的柔性數組,這裏存儲了整個字符串,經過這個方式保證字符串所在的內存地址是與該結構體內存地址緊密相連的,減小了去另一個塊內存取值的時間。
(ps:留個小疑問,gdb就能夠追蹤到。柔性數組是否佔內存空間?zend_string的對齊後是什麼結構?總體佔多大?)
瞭解過結構自己,能夠打印一下內容來看看,如圖
該地址內存儲的確實是hello world,爲何gc中的refcount是0呢?
緣由是這樣的:
修改一下代碼,看一下臨時字符串
<?php $a = "hello world".time(); echo $a;
打印一下這個變量的zval,refcount爲1,如圖
對於臨時字符串,應該是每有一個被賦值的變量時,該zend_string中的引用計數+1,而且在引用計數爲0時,釋放這塊內存。
<?php $a = "hello world".time(); echo $a; $b = $a; echo $b;
當爲$a賦值完成時,$a,在棧上第一個位置,類型爲6,IS_STRING,取value中的str,地址爲:**0x7ffff4402c30**,能夠看到內容,zend_string引用計數爲1。
當爲$b賦值完成時,$b在棧上第二個位置,類型爲6,IS_STRING,取value中的str一樣地址爲:**0x7ffff4402c30**,zend_string引用計數爲2。
大體引用狀況能夠畫出:
對於變量直接賦值,上面已經畫出了引用關係,那麼若是是引用類型呢?
<?php $a = "hello world".time(); echo $a; $b = &$a; echo $b;
當爲$a賦值完成時,$a,在棧上第一個位置,類型爲6,IS_STRING,取value中的str,地址爲:**0x7ffff4402c30**,能夠看到內容,zend_string引用計數爲1。
當$b賦值爲引用類型時,$b在棧上第二個位置,類型爲10 , IS_REFERENCE,取value中的ref能夠看到內容。
這時$a的類型是否發生改變?是否仍是字符串類型?直接打印$a看一下。這時$a的類型變成了10,IS_REFERENCE,打印value中的ref,地址與$b的ref相同!
在$b引用$a的時候,$a與$b都變成引用類型,該引用類型指向了中有一個zval,類型爲6,IS_STRING,value中的str指向了一個zend_string,而且zend_string引用計數爲1.
大體引用狀況如圖:
<?php $a = 「string」; $b = 「double」; echo $a; echo $b;
以咱們上面的結論,$a與$b都屬於常量字符串。
打印$a的zend_string,如圖
打印$b的zend_string,如圖
可見$b符合預期,可是$a顛覆了以上的理論。
那問題出在了哪?
通過GDB的追蹤,能夠看到a和b都在棧上,而且都是string類型。
可是,其中value中的str地址有很大不同。
首先看變量a
在棧的第一個位置,str的值是 0x11522c0
其次看變量b
在棧上第二個位置,str的值是 0x7ffff4401880
瞭解PHP的內存分配的話,能夠看出b的字符串,分配在了 0x7ffff440000 這個chunk上,屬於第一個page頁,0x7ffff4401000
而a的字符串很顯然不是這個規則,他沒有分配在chunk上,而是很特殊的一個地址。
因此string這個字符串,不是_emalloc分配的。
那麼採用個笨辦法,我把 0x11522c0強轉 (zend_string *)0x11522c0 ,而後看裏的值何時放進去的。
PHP版本 7.1.0
第一個節點: php_cli.c中的 1345行
sapi_module->startup(sapi_module)
第二個節點: php_cli.c 中的424 行
php_module_startup(sapi_module, NULL, 0)
第三個節點: main.c 中的 2123行
zend_startup(&zuf, NULL);
第四個節點: zend.c 中的768行
zend_interned_strings_init();
很接近了哦
第五個節點: zend_string.c中的103
zend_intern_known_strings(known_strings, (sizeof(known_strings)
在這裏打印一下,know_strings,這裏能夠看到,file,line,function,class,object等等,以及string在這裏就初始化了!
對應聲明的地址在 zend_string.h 383行
這裏尚未初始化字面量,因而這些字符串與字面量的狀況有些不同。
一樣是字符串在PHP中有不少種不一樣狀況。