前言中提到變量的三個基本特性,其中的有一個特性為變量的類型,變量都有特定的類型,如:字符串、數組、對象等等。編程語言的類型系統可以分為強類型和弱類型兩種:
強類型語言是一旦某個變量被申明為某個類型的變量,在程序運行過程中,就不能將該變量的類型以外的值賦予給它(當然并不完全如此,這可能會涉及到類型的轉換,后面的小節會有相應介紹),C/C++/Java等語言就屬于這類。
PHP及Ruby,JavaScript等腳本語言屬于弱類型語言:一個變量可以表示任意的數據類型。
PHP之所以成為一個簡單而強大的語言,很大一部分的原因是它擁有弱類型的變量。但是有些時候這也是一把雙刃劍,使用不當也會帶來一些問題。就像儀器一樣,越是功能強大,出現錯誤的可能性也就越大。
在官方的PHP實現內部,所有變量使用同一種數據結構(zval)來保存,而這個結構同時表示PHP中的各種數據類型。它不僅僅包含變量的值,也包含變量的類型。這就是PHP弱類型的核心。
那zval結構具體是如何實現弱類型的呢,下面我們一起來揭開面紗。
## 一. PHP變量類型及存儲結構
PHP在聲明或使用變量的時候,并不需要顯式指明其數據類型。
PHP是弱類型語言,這并不表示PHP沒有類型,在PHP中,存在8種變量類型,可以分為三類
**標量類型**: _boolean_、_integer_、_float(double)_、_string_
**復合類型**: _array_、_object_
**特殊類型**: _resource_、_NULL_
官方PHP是用C實現的,而C是強類型的語言,那這是怎么實現PHP中的弱類型的呢?
### 1. 變量存儲結構
變量的值存儲到以下所示zval結構體中。zval結構體定義在Zend/zend.h文件,其結構如下:
typedef struct _zval_struct zval;
...
struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};
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用戶空間導出了方法或類。
### 2.變量類型
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中的所有邏輯都圍繞變量來進行的,這樣的話, 內存浪費將是十分大的。這種做法成本小但收益非常大。
各種類型的數據會使用不同的方法來進行變量值的存儲,其對應賦值方式如下:
- 一般類型
<table><thead><tr><th align="left">變量類型</th> <th align="center">宏</th> <th align="left"/></tr></thead><tbody><tr><td align="left">boolean</td> <td align="left">ZVAL_BOOL</td> <td align="left" rowspan="3"> 布爾型/整型的變量值存儲于(zval).value.lval中,其類型也會以相應的IS_*進行存儲。 <pre class="c"> Z_TYPE_P(z)=IS_BOOL/LONG; Z_LVAL_P(z)=((b)!=0); </pre></td></tr><tr><td align="left">integer</td> <td align="left">ZVAL_LONG</td></tr><tr><td align="left">float</td> <td align="left">ZVAL_DOUBLE</td></tr><tr><td align="left">null</td> <td align="left">ZVAL_NULL</td> <td align="left"> NULL值的變量值不需要存儲,只需要把(zval).type標為IS_NULL。 <pre class="c"> Z_TYPE_P(z)=IS_NULL; </pre> </td></tr><tr><td align="left">resource</td> <td align="left">ZVAL_RESOURCE</td> <td align="left"> 資源類型的存儲與其他一般變量無異,但其初始化及存取實現則不同。 <pre class="c"> Z_TYPE_P(z) = IS_RESOURCE; Z_LVAL_P(z) = l; </pre> </td></tr></tbody></table>
- 字符串String
字符串的類型標示和其他數據類型一樣,不過在存儲字符串時多了一個字符串長度的字段。
struct {
char *val;
int len;
} str;
> C中字符串是以\0結尾的字符數組,這里多存儲了字符串的長度,這和我們在設計數據庫時增加的冗余字段異曲同工。 因為要實時獲取到字符串的長度的時間復雜度是O(n),而字符串的操作在PHP中是非常頻繁的,這樣能避免重復計算字符串的長度, 這能節省大量的時間,是空間換時間的做法。
這么看在PHP中strlen()函數可以在常數時間內獲取到字符串的長度。 計算機語言中字符串的操作都非常之多,所以大部分高級語言中都會存儲字符串的長度。
- 數組Array
數組是PHP中最常用,也是最強大變量類型,它可以存儲其他類型的數據,而且提供各種內置操作函數。數組的存儲相對于其他變量要復雜一些,數組的值存儲在zvalue_value.ht字段中,它是一個HashTable類型的數據。PHP的數組使用哈希表來存儲關聯數據。哈希表是一種高效的鍵值對存儲結構。PHP的哈希表實現中使用了兩個數據結構HashTable和Bucket。PHP所有的工作都由哈希表實現,在下節HashTable中將進行哈希表基本概念的介紹以及PHP的哈希表實現。
- 對象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不實現應該也是做了一個權衡。
- 第一章 準備工作和背景知識
- 第一節 環境搭建
- 第二節 源碼結構、閱讀代碼方法
- 第三節 常用代碼
- 第四節 小結
- 第二章 用戶代碼的執行
- 第一節 生命周期和Zend引擎
- 第二節 SAPI概述
- Apache模塊
- 嵌入式
- FastCGI
- 第三節 PHP腳本的執行
- 詞法分析和語法分析
- opcode
- opcode處理函數查找
- 第四節 小結
- 第三章 變量及數據類型
- 第一節 變量的結構和類型
- 哈希表(HashTable)
- PHP的哈希表實現
- 鏈表簡介
- 第二節 常量
- 第三節 預定義變量
- 第四節 靜態變量
- 第五節 類型提示的實現
- 第六節 變量的生命周期
- 變量的賦值和銷毀
- 變量的作用域
- global語句
- 第七節 數據類型轉換
- 第八節 小結
- 第四章 函數的實現
- 第一節 函數的內部結構
- 函數的內部結構
- 函數間的轉換
- 第二節 函數的定義,傳參及返回值
- 函數的定義
- 函數的參數
- 函數的返回值
- 第三節 函數的調用和執行
- 第四節 匿名函數及閉包
- 第五節 小結
- 第五章 類和面向對象
- 第一節 類的結構和實現
- 第二節 類的成員變量及方法
- 第三節 訪問控制的實現
- 第四節 類的繼承,多態及抽象類
- 第五節 魔術方法,延遲綁定及靜態成員
- 第六節 PHP保留類及特殊類
- 第七節 對象
- 第八節 命名空間
- 第九節 標準類
- 第十節 小結
- 第六章 內存管理
- 第一節 內存管理概述
- 第二節 PHP中的內存管理
- 第三節 內存使用:申請和銷毀
- 第四節 垃圾回收
- 新的垃圾回收
- 第五節 內存管理中的緩存
- 第六節 寫時復制(Copy On Write)
- 第七節 內存泄漏
- 第八節 小結
- 第七章 Zend虛擬機
- 第一節 Zend虛擬機概述
- 第二節 語法的實現
- 詞法解析
- 語法分析
- 實現自己的語法
- 第三節 中間代碼的執行
- 第四節 PHP代碼的加密解密
- 第五節 小結
- 第八章 線程安全
- 第二節 線程,進程和并發
- 第三節 PHP中的線程安全
- 第九章 錯誤和異常處理
- 第十章 輸出緩沖
- 第十六章 PHP語言特性的實現
- 第一節 循環語句
- foreach的實現
- 第二十章 怎么樣系列(how to)
- 附錄
- 附錄A PHP及Zend API
- 附錄B PHP的歷史
- 附錄C VLD擴展使用指南
- 附錄D 怎樣為PHP貢獻
- 附錄E phpt測試文件說明
- 附錄F PHP5.4新功能升級解析
- 附錄G:re2c中文手冊