php 核心探祕之 PHP_FUNCTION 巨集

daryl發表於2019-05-09

本人也只是個初入門的菜鳥,因對技術有著嚮往,故在“無趣”的工作之餘,儘自己所能提升自己。由於我的 C 語言功底也有限,故本文的深度也有限,如有幸得大牛閱讀,還望指導一二,小弟感激不盡。

PHP 的函式

作為 PHPer,我們幾乎每天都在寫函式,我們一定會好奇,那些 PHP 內建的函式,是長什麼樣子的。如果寫過 PHP 擴充套件的話,一定知道這個巨集:PHP_FUNCTION。在定義一個函式的時候,這樣來使用這個巨集。例如 array_change_key_case,它的定義是這樣的:PHP_FUNCTION(array_change_key_case)。沒錯,就是這麼簡單。但是,在這個簡單的背後,卻沒有這麼簡單。

PHP_FUNCTION 追根溯源

巨集

相信對這篇文章感興趣的同學,一定多少對 C 語言以及它的巨集定義有一定的瞭解。如果沒有,也不要緊,我這裡來簡單解釋一下,什麼是巨集。

C 語言中的巨集,我認為,可以理解為一種簡單的封裝。通過巨集定義,可以對開發者隱去一些細節,讓開發者在使用簡單的語法來完成重複的複雜的編碼。當然,巨集定義還有其它的用途,但是,我們在 PHP_FUNCTION 涉及到的就是這個作用。有下面的程式碼。

#define TEST(test) void test(int a)

TEST(haha)

巨集,就是完全的替換,即使用後面的語句替換前面的。那麼對於下面的 TEST(haha) 就相當於下面的樣子。

void haha(int a)

PHP_FUNCTION 的定義

首先,我們要定義函式,這樣使用這個巨集。

PHP_FUNCTION(array_change_key_case)
{
    // TODO
}

我們在 php-src/main/php.h 中找到了下面的定義。

#define PHP_FUNCTION ZEND_FUNCTION

也就是說,這裡用 ZEND_FUNCTION 替換了 PHP_FUNCTION 這個巨集。所以,我們的定義就相當於變成了這樣。

ZEND_FUNCTION(array_change_key_case)
{
    // TODO
}

我們繼續往下找,因為,這裡還是巨集,我們並沒有看到我們希望看到的程式碼。我們可以在 php-src/Zend/zend_API.h 中找到下面的定義。

#define ZEND_FN(name) zif_##name
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION(ZEND_FN(name))
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

我們看到,在巨集定義中,使用了另外的巨集。不要怕,還是一個詞,替換。我們按照這樣的步驟來。(## 是一個連線符,它的作用是,是將它前面的與後面的,按照字串的方式連線起來。

  1. 替換 ZEND_FUNCTION

ZEND_NAMED_FUNCTION(ZEND_FN(name))
{
    // TODO
}
  1. 替換 ZEND_FN

ZEND_NAMED_FUNCTION(zif_array_change_key_case)
{
    // TODO
}
  1. 替換 ZEND_NAMED_FUNCTION

void zif_array_change_key_case(INTERNAL_FUNCTION_PARAMETERS)
{
    // TODO
}

到這裡,我們可以看到,這裡已經基本和我們熟悉的函式定義差不多了,不過,這還沒完,以為,這裡還有巨集,那就是 INTERNAL_FUNCTION_PARAMETERS。我們找到 php-src/Zend/zend.h,可以找到 INTERNAL_FUNCTION_PARAMETERS 的巨集定義。

#define INTERNAL_FUNCTION_PARAMETERS zend_execute_data *execute_data, zval *return_value

好了,依然按照替換的原則,我們就可以將函式定義變成這樣了。

void zif_array_change_key_case(zend_execute_data *execute_data, zval *return_value)
{
    // TODO
}

看,整個函式的定義,已經完全沒有巨集了,這已經是我們在熟悉不過的 C 語言函式的定義了。這就是
PHP_FUNCTION 的整個定義的過程。

execute_data 和 return_value

return_value,顧名思義,就是定義的 PHP 函式的返回值。而 execute_data,按照我的理解,就是 Zend 內部的一個呼叫棧,而在執行這個函式的時候,指向的是這個函式的棧幀。具體的細節,暫時在這裡先不考慮,有興趣的同學可以來這裡看一下。深入理解 PHP 核心

後記

我始終認為,對於一個 PHPer 來說,C 語言是一項必不可少的技能。理解 PHP 的核心,對於我們編寫出高質量的程式碼,起到了關鍵的作用。所以,我現在開始研究 PHP 的原始碼實現了。我希望我能通過這些文章,記錄下我理解原始碼的瞬間,也希望我的文章能讓更多的 PHPer,進入到 PHP 核心的世界。

相關文章