PHP內核探索:變量存儲與類型

<?php
$foo = 10;
$bar = 20;
 
function change() {
    global $foo;
	//echo '函數內部$foo = '.$foo.'<br />';
	//若是不把$bar定義爲global變量,函數體內是不能訪問$bar的
    $bar = 0;
    $foo++;
}
 
change();
echo $foo, ' ', $bar;
?>

程序輸出 11 20。緣由是,方法內部沒法訪問$bar變量,因此它的值仍是20。使用global以後,能夠取得$foo的值,自增後$foo的值就是11。php

Global的做用是定義全局變量,可是這個全局變量不是應用於整個網站,而是應用於當前頁面,包括include或require的全部文件。數據庫

前言中提到變量的三個基本特性,其中的有一個特性爲變量的類型,變量都有特定的類型, 如:字符串、數組、對象等等。編程語言的類型系統能夠分爲強類型和弱類型兩種:編程

強類型語言是一旦某個變量被申明爲某個類型的變量,則在程序運行過程當中,該不能將該變量的類型之外的值賦予給它 (固然並不徹底如此,這可能會涉及到類型的轉換,後面的小節會有相應介紹),C/C++/Java等語言就屬於這類。數組

PHP及Ruby,JavaScript等腳本語言屬於弱類型語言:一個變量能夠表示任意的數據類型。數據結構

PHP之因此成爲一個簡單而強大的語言,很大一部分的緣由是它擁有弱類型的變量。 可是有些時候這也是一把雙刃劍,使用不當也會帶來一些問題。就像儀器同樣,越是功能強大, 出現錯誤的可能性也就越大。編程語言

在官方的PHP實現內部,全部變量使用同一種數據結構(zval)來保存,而這個結構同時表示PHP中的各類數據類型。 它不只僅包含變量的值,也包含變量的類型。這就是PHP弱類型的核心。函數

那zval結構具體是如何實現弱類型的呢,下面咱們一塊兒來揭開面紗。性能

變量存儲結構

PHP在聲明或使用變量的時候,並不須要顯式指明其數據類型。網站

PHP是弱類型語言,這並不表示PHP沒有類型,在PHP中,存在8種變量類型,能夠分爲三類ui

  • * 標量類型:booleanintegerfloat(double)string 

  • * 複合類型: arrayobject 

  • * 特殊類型: resourceNULL

官方PHP是用C實現的,而C是強類型的語言,那這是怎麼實現PHP中的弱類型的呢?

變量的值存儲到如下所示zval結構體中。 zval結構體定義在Zend/zend.h文件,其結構以下:

1 typedef struct _zval_struct zval;
2 ...
3 struct _zval_struct {
4     /* Variable information */
5     zvalue_value value;     /* value */
6     zend_uint refcount__gc;
7     zend_uchar type;    /* active type */
8     zend_uchar is_ref__gc;
9 };

PHP使用這個結構來存儲變量的全部數據。和其餘編譯性靜態語言不一樣, PHP在存儲變量時將PHP用戶空間的變量類型也保存在同一個結構體中。這樣咱們就能經過這些信息獲取到變量的類型。

zval結構體中有四個字段,其含義分別爲:

屬性名 含義 默認值
refcount__gc 表示引用計數 1
is_ref__gc 表示是否爲引用 0
value 存儲變量的值
type 變量具體的類型

在PHP5.3以後,引入了新的垃圾收集機制,引用計數和引用的字段名改成refcount__gc和is_ref__gc。在此以前爲refcount和is__ref。

而變量的值則存儲在另一個結構體zvalue_value中。值存儲見下面的介紹。

PHP用戶空間指的在PHP語言這一層面,而本書中大部分地方都在探討PHP的實現。 這些實現能夠理解爲內核空間。因爲PHP使用C實現,而這個空間的範疇就會限制在C語言。 而PHP用戶空間則會受限於PHP語法及功能提供的範疇以內。 例若有些PHP擴展會提供一些PHP函數或者類,這就是向PHP用戶空間導出了方法或類。

變量類型

zval結構體的type字段就是實現弱類型最關鍵的字段了,type的值能夠爲: IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT和IS_RESOURCE 之一。 從字面上就很好理解,他們只是類型的惟一標示,根據類型的不一樣將不一樣的值存儲到value字段。 除此以外,和他們定義在一塊兒的類型還有IS_CONSTANT和IS_CONSTANT_ARRAY。

這和咱們設計數據庫時的作法相似,爲了不重複設計相似的表,使用一個標示字段來記錄不一樣類型的數據。

變量的值存儲

前面提到變量的值存儲在zvalue_value聯合體中,結構體定義以下:

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;

這裏使用聯合體而不是用結構體是出於空間利用率的考慮,由於一個變量同時只能屬於一種類型。 若是使用結構體的話將會沒必要要的浪費空間,而PHP中的全部邏輯都圍繞變量來進行的,這樣的話, 內存浪費將是十分大的。這種作法成本小但收益很是大。

各類類型的數據會使用不一樣的方法來進行變量值的存儲,其對應賦值方式以下:

1. 通常類型

變量類型 ?
boolean ZVAL_BOOL 布爾型/整型的變量值存儲於(zval).value.lval中,其類型也會以相應的IS_*進行存儲。
 Z_TYPE_P(z)=IS_BOOL/LONG;  Z_LVAL_P(z)=((b)!=0);
integer ZVAL_LONG
float ZVAL_DOUBLE
null ZVAL_NULL NULL值的變量值不須要存儲,只須要把(zval).type標爲IS_NULL。
 Z_TYPE_P(z)=IS_NULL;
resource ZVAL_RESOURCE 資源類型的存儲與其餘通常變量無異,但其初始化及存取實現則不一樣。
 Z_TYPE_P(z) = IS_RESOURCE;  Z_LVAL_P(z) = l;

2. 字符串Sting

字符串的類型標示和其餘數據類型同樣,不過在存儲字符串時多了一個字符串長度的字段。

struct {
    char *val;
    int len;
} str;

C中字符串是以\0結尾的字符數組,這裏多存儲了字符串的長度,這和咱們在設計數據庫時增長的冗餘字段殊途同歸。 由於要實時獲取到字符串的長度的時間複雜度是O(n),而字符串的操做在PHP中是很是頻繁的,這樣能避免重複計算字符串的長度, 這能節省大量的時間,是空間換時間的作法。 這麼看在PHP中strlen()函數能夠在常數時間內獲取到字符串的長度。 計算機語言中字符串的操做都很是之多,因此大部分高級語言中都會存儲字符串的長度。

3. 數組Array

數組是PHP中最經常使用,也是最強大變量類型,它能夠存儲其餘類型的數據,並且提供各類內置操做函數。數組的存儲相對於其餘變量要複雜一些, 數組的值存儲在zvalue_value.ht字段中,它是一個HashTable類型的數據。 PHP的數組使用哈希表來存儲關聯數據。哈希表是一種高效的鍵值對存儲結構。PHP的哈希表實現中使用了兩個數據結構HashTable和Bucket。 PHP全部的工做都由哈希表實現,在下節HashTable中將進行哈希表基本概念的介紹以及PHP的哈希表實現。

4. 對象Object

在面嚮對象語言中,咱們能本身定義本身須要的數據類型,包括類的屬性,方法等數據。而對象則是類的一個具體實現。 對象有自身的狀態和所能完成的操做。

PHP的對象是一種複合型的數據,使用一種zend_object_value的結構體來存放。其定義以下:

typedef struct _zend_object_value {
    zend_object_handle handle;  //  unsigned int類型,EG(objects_store).object_buckets的索引
    zend_object_handlers *handlers;
} zend_object_value;

PHP的對象只有在運行時纔會被建立,前面的章節介紹了EG宏,這是一個全局結構體用於保存在運行時的數據。 其中就包括了用來保存全部被建立的對象的對象池,EG(objects_store),而object對象值內容的zend_object_handle域就是當前 對象在對象池中所在的索引,handlers字段則是將對象進行操做時的處理函數保存起來。 這個結構體及對象相關的類的結構_zend_class_entry,後面會介紹到。

PHP的弱變量容器的實現方式是兼容幷包的形式體現,針對每種類型的變量都有其對應的標記和存儲空間。 使用強類型的語言在效率上一般會比弱類型高,由於不少信息能在運行以前就能肯定,這也能幫助排除程序錯誤。 而這帶來的問題是編寫代碼相對會受制約。

PHP主要的用途是做爲Web開發語言,在普通的Web應用中瓶頸一般在業務和數據訪問這一層。不過在大型應用下語言也會是一個關鍵因素。 facebook所以就使用了本身的php實現。將PHP編譯爲C++代碼來提升性能。不過facebook的hiphop並非完整的php實現, 因爲它是直接將php編譯爲C++,有一些PHP的動態特性好比eval結構就沒法實現。固然非要實現也是有方法的, hiphop不實現應該也是作了一個權衡。

相關文章
相關標籤/搜索