文章來自:http://www.hoohack.me/2016/02/04/phps-source-code-for-php-developers-chphp
原文:http://blog.ircmaxell.com/2012/03/phps-source-code-for-php-developers.htmlhtml
做爲一個開發者,我發如今個人平常工做中愈來愈多地查看PHP的源碼。在爲了弄清楚奇怪的邊界問題和爲何某些問題應該發生的卻沒有發生而去理解背後究竟發生了什麼事情的時候很是有用。在文檔缺失、不完整或者錯誤的狀況下也頗有用。所以,我已經決定經過一系列的文章來分享我學到的知識,給予PHP開發者們足夠的知識去真正閱讀PHP的C語言源碼。你並不須要有C語言的基礎(咱們會總結一些基礎),但若是有的話會更有幫助。mysql
這是這個系列的第一篇文章。在這篇文章,咱們會談論PHP程序的基礎:在哪裏找到它,基本的代碼結構和一些最基礎的C語言概念。須要說明的是,這一系列文章的目標是得到源碼的閱讀理解能力。這意味着爲了過一下某些點,某些概念會被簡化而不是太複雜的描述。這不會給閱讀形成明顯的差別,但若是你想爲源碼作貢獻,則還有更多的知識須要補充。在我作簡化的時候,我會盡可能指出這些簡化。sql
另外,這系列文章是基於5.4版本的源碼,在不一樣版本中,大部分概念都是同樣的,但這裏,咱們須要針對此次的文章有一個版本的定義(爲了讓新的版本出來後接下來的文章更容易地遵循)。數組
那麼,咱們能夠開始了吧?svn
下載PHP源碼最簡單的方式是經過PHP的SVN倉庫。對於這此文章,咱們檢出(check out)了5.4的分支。這對於成爲PHP的前沿或者真正的開發PHP(解決bugs,實現特性等等)來講是很是棒的。值得注意的是,PHP社區正在(這篇文章正在寫的時候)將源碼遷移到GIT倉庫中。一旦遷移完成,我會更新這篇文章以達到標準。(譯者注:譯者翻譯的時候PHP已經遷移到GIT倉庫了)。函數
事實上,下載源碼對咱們的目的來講並非真正的有用。咱們不想編輯它,咱們只是想使用它和跟蹤它是如何運行的。咱們能夠下載它,而後導入到一個好的IDE中,在這些IDE中咱們能夠點擊跳到函數的定義和聲明,當我發現這比想象中略困難。我有一個更好的解決方案。工具
事實證實,PHP社區在維護一個對於咱們來講一個很是好的工具。那就是lxr.php.net。這主要是一個自動生成可搜索的源碼列表,並且有語法高亮和函數所有有連接的。這個是我幾乎只用來瀏覽C源碼的工具,實在太棒(即便在我寫補丁的時候,我依然到lxr而不是我正在開發的代碼庫)。咱們還不會講到如何作更有效的搜索,但咱們會在談論PHP核心函數的時候講到。優化
從這裏開始,咱們將開始談論PHP5.4。爲了達到這目的,咱們會使用這個lxr連接做爲其餘文章的基礎。當我提到「5.4的根目錄」的時候,我就是說這個頁面。操作系統
那麼,既然咱們能夠查看源碼目錄了,那麼咱們來談談這裏面都有什麼吧。
那麼,當你查看列在5.4的根目錄的文件和目錄時,還有不少能夠研究。我但願你只關注兩個目錄:ext和Zend。其餘的文件和目錄對於PHP擴展和開發來講很重要,但對於咱們的目的來講,咱們徹底能夠忽略它們。那麼,爲何這兩個目錄那麼重要呢?
PHP程序被分爲,你猜對了,兩個主要的部分。第一部分是Zend引擎,控制PHP代碼運行時候的運行環境。它處理PHP提供的全部「語言層」的特性,包括:變量,表達式,語法解析,代碼執行和錯誤處理。沒有這個引擎,就沒有PHP。引擎的源碼放在了Zend目錄。
PHP第二個核心的部分,是包含在PHP裏面的擴展。這些擴展包括咱們能夠在PHP調用的每個核心函數(例如strpos,substr,array_diff,mysql_connect等等)。也包括核心的類(MySQLi,SplFixedArray,PDO等等)。
在覈心代碼中,決定在哪裏找到你想查看的功能最簡單的方法是,查看PHP的文檔首頁。PHP的文檔也被分爲兩個主要的部分(爲了達到咱們的目的),語言參考和函數參考。做爲一個龐大的歸納,若是你想查看的是在語言參考中的定義,頗有可能能夠在Zend文件夾找到。若是是在函數參考中,能夠在ext文件夾中找到。
這部分不是爲了成爲C的入門,而是一個「讀者的配套指南」。有以下概念:
在C裏面,變量是靜態和強類型的。這意味着變量必需要使用一個類型定義以後才能使用。一旦定義以後,你不能改變它的類型(你能夠在以後轉換成其餘類型,但你須要使用不一樣的變量來實現)。由於,在C語言裏面,變量並不真實地存在。它們只是爲了咱們使用的方便的內存地址的標籤。正由於如此,C語言沒有PHP中的引用。取而代之,它有指針。爲了咱們的目的,把指針想象成指向其餘變量的變量。把它看成PHP中變量的變量。
那麼,經過上面的描述,咱們來談論一下變量的語法。C語言沒有使用任何的前綴來標識變量。所以,要說出它們的不一樣的惟一方式(爲了達到咱們的目的)是查看它們的定義。若是你在函數的頂部(或者函數的聲明)看到在類型和空格以後的字符,那就是變量。一個要說明的關鍵點是變量名前面能夠有一個或這多個符號。星號(*)代表變量是指向某個類型的指針(一個引用)。兩個星號代表變量是指向指針的指針。三個星號代表變量是指向一個指向其餘指針的指針。
這個間接尋址很是重要,由於PHP內部使用不少的雙層指針。這是由於引擎須要可以傳遞塊數據(PHP變量),和全部有趣的類型如PHP引用,寫時複製以及對象引用等等。所以,只要意識到**ptr意味着咱們正使用兩層的引用(不是變量的引用,而是一個數據引用的引用)。這又一點迷惑,但若是引用對你來講是徹底新的知識,我建議你閱讀一下這方面的知識(儘管咱們的目的是不用必需閱讀C)。會有幫助的。
如今,另外一個理解指針的事情是它們是如何在C的數組裏應用的(不是PHP的數組,而是C語言中的數組)。由於指針是內存地址,咱們能夠經過分配一塊的內存來定義一個數組,而後經過遞增指針來遍歷它。正常狀況下,咱們可使用表明一個字符(8位)的C的數據類型char來存儲字符串中的一個字符。但咱們也能夠像使用數組那樣使用它來訪問字符串後面的字節。所以,咱們能夠只在第一個字節裏存儲一個指針而不是存儲正一個字符串在變量中。而後,咱們能夠遞增指針(增長它的內存地址)來遍歷整個字符串。
char *foo = "test"; // foo 是指向"t"在內存的片斷保存"test"的指針 // 要訪問"e",咱們能夠經過下面的方式: char e = foo[1]; char e = *(foo + 1); char e = *(++foo);
要另外閱讀C語言重點的變量和指針,查看這本很好的免費書籍。
C在編譯以前使用一步叫作「預處理」的步驟。這一步包含優化和根據你傳遞給編譯器的選項動態使用部分代碼。咱們將談論兩個主要的預處理器說明:條件語句和宏。
條件語句容許代碼在編譯輸出或者不是基於定義時被引入。這看起來很像下面的例子。這容許不一樣的代碼根據不一樣的操做系統被使用(所以儘管它們使用不一樣的API,也能夠在Windows和Linux中很好的使用)。另外,它容許一部分代碼被引入或者不是基於定義的指示。事實上,這是配置步驟中如何編譯PHP的執行過程。
#define FOO 1 #if FOO Foo is defined and not 0 #else Foo is not defined or is 0 #endif #ifdef FOO Foo is defined #else Foo is not defined #endif
另外一個說明我叫它作宏。這是最簡單簡化代碼的迷你函數。它們不是真正的函數,可是在編譯預處理是會執行簡單的文本替換。所以,宏不會真正地調用函數。你能夠爲函數定義寫一個宏(事實上,PHP就是這麼作的,但咱們會在後面的文章中深刻了解這個)。我想說的是,宏容許在預處理編譯時使用更簡單的代碼。
#define FOO(a) ((a) + 1) int b = FOO(1); // Converted to int b = 1 + 1
最後這一部分,咱們須要瞭解的是兩種在C源碼使用的類型的文件。主要有兩種文件:.c和.h。.c文件是包含了源碼準備編譯的文件。一般來講,.c文件包含了不能分享到其餘文件的私有函數的實現。.h(或者說頭文件)定義了在.c文件中能夠被其餘文件看到的函數,包括預處理宏。頭文件定義公共API的方式,是經過不使用函數體從新聲明函數的簽名(跟PHP中的接口和抽象方法類似)。這樣,源碼就能夠經過頭文件連接在一塊兒了。
這個系列的下一部分文章,咱們即將討論內部函數在C裏面是怎麼定義的。所以你能夠跳到任意的內部函數(好比strlen)查看它的定義和它是如何工做的。保持這個節奏。