PHP源碼閱讀(一):str_split函數

注:源碼版本:php5.6.33。php

函數簡介

str_split 原型:html

array str_split ( string $string [, int $split_length = 1 ] )

說明:將一個字符串轉換爲數組。 參數:string爲輸入字符串。split_length是每一段的長度。數組

str_split() 使用範例 :安全

$str  =  "Hello Friend" ;

$arr1  =  str_split ( $str );
$arr2  =  str_split ( $str ,  3 );

print_r ( $arr1 );
print_r ( $arr2 );

以上例程會輸出:函數

Array
(
    [0] => H
    [1] => e
    [2] => l
    [3] => l
    [4] => o
    [5] =>
    [6] => F
    [7] => r
    [8] => i
    [9] => e
    [10] => n
    [11] => d
)Array
(
    [0] => Hel
    [1] => lo
    [2] => Fri
    [3] => end
)

對應的C源碼在 ext/standard/string.c 5568行。這裏我貼出來:ui

PHP_FUNCTION(str_split)
{
    char *str;
    int str_len;
    long split_length = 1;
    char *p;
    int n_reg_segments;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &split_length) == FAILURE) {
        return;
    }

    if (split_length <= 0) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "The length of each segment must be greater than zero");
        RETURN_FALSE;
    }

    array_init_size(return_value, ((str_len - 1) / split_length) + 1);

    if (split_length >= str_len) {
        add_next_index_stringl(return_value, str, str_len, 1);
        return;
    }

    n_reg_segments = str_len / split_length;
    p = str;

    while (n_reg_segments-- > 0) {
        add_next_index_stringl(return_value, p, split_length, 1);
        p += split_length;
    }

    if (p != (str + str_len)) {
        add_next_index_stringl(return_value, p, (str + str_len - p), 1);
    }
}

zend_parse_parameters

首先看參數解析部分:.net

zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &str, &str_len, &split_length)

一、第一個參數咱們使用默認值。下面是緣由:線程

傳遞給 zend_parse_parameters() 的第一個參數是用戶實際傳遞到函數的參數數量。此數值作爲 ht 參數傳給函數,但就像上面討論的那樣,應使用作爲實現細節的 ZEND_NUM_ARGS()。爲了與 PHP 的線程隔離、線程安全資源管理器兼容,還要用 TSRMLS_CC 傳遞線程上下文。與其餘函數不一樣,它不能是最後的參數,由於在 zend_parse_parameters 內要求有不定數量的參數——依賴於要讀取的用戶參數的數量。scala

二、第二個參數定義所要求的參數。指針

每一個參數都由字符串中的一個字符表示其類型。 若是但願一個字符串參數,則在此類型說明只不過是個 "s"。

這裏的s|l表示接受一個字符串和它的長度,另外再取得一個可選的長整數。|表示可選。

相關全部類型說明符和對應的附加的 C 語言類型的文檔可在源代碼發佈包中的文件 README.PARAMETER_PARSING_API 中找到。大多數重要類型可見下表。

zend_parse_parameters() 類型說明符

修飾符 對應C裏的數據類型 描述
b zend_bool Boolean 值
l long integer (long) 值
d double float (double) 值
s char*, int 二進制的安全串。前者接收指針,後者接收長度
h HashTable* 數組的哈希表
r zval* Resource 資源
a zval* Array 數組
o zval* Object instance 對象
O zval, zend_class_entry Object instance of a specified type 特定類型的對象
z zval* Non-specific zval 任意類型~
Z zval** zval**類型
f zval** 表示函數、方法名稱,PHP5.3以前沒有的

bldsh這幾個比較經常使用,須要熟記。s比較特殊,須要用兩個參數來接收。

若是有多個參數,類型說明符能夠有多個。例如lsz表示取得一個長整數,一個字符串和它的長度,再取得一個 zval 值。類型說明符還有幾個特殊標記:

| - 代表剩下的參數都是可選參數。若是用戶沒有傳進來這些參數值,那麼這些值就會被初始化成默認值。
 
/ - 代表參數解析函數將會對剩下的參數以 SEPARATE_ZVAL_IF_NOT_REF() 的方式來提供這個參數的一份拷貝,除非這些參數是一個引用。
 
! - 代表剩下的參數容許被設定爲 NULL(僅用在 a、o、O、r和z身上)。若是用戶傳進來了一個 NULL 值,則存儲該參數的變量將會設置爲 NULL。

三、最後一個參數是傳遞一個或多個指針給要填充變量值的 C 變量,或提供更多細節。好比字符串,事實上的字符串,老是以 NULL 結尾,以 char*,且其長度是除 NULL 字節外的 int 型值。

參考:

函數返回值

PHP擴展開發裏不是直接以return的形式返回值的,zend引擎在每一個zif函數聲明裏加了一個zval*類型的形參,名爲return_value,專門來解決返回值這個問題。

ZEND_FUNCTION自己並無經過return關鍵字返回任何有價值的東西,它只不過是在運行時修改了return_value指針所指向的變量的值而已,而內核則會把return_value指向的變量做爲用戶端調用此函數
後的獲得的返回值。ZVAL_LONG()等宏是對一類操做的封裝,展開後應該就是下面這樣:

Z_TYPE_P(return_value) = IS_LONG;
Z_LVAL_P(return_value) = 42;

//更完全的講,應該是這樣的:
return_value->type = IS_LONG;
return_value->value.lval = 42;

其它的還有:

//這些宏都定義在Zend/zend_API.h文件裏
#define RETVAL_RESOURCE(l)              ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b)                  ZVAL_BOOL(return_value, b)
#define RETVAL_NULL()                   ZVAL_NULL(return_value)
#define RETVAL_LONG(l)                  ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d)                ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate)         ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate)     ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING()           ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor)     ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE                    ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE                     ZVAL_BOOL(return_value, 1)

#define RETURN_RESOURCE(l)              { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b)                  { RETVAL_BOOL(b); return; }
#define RETURN_NULL()                   { RETVAL_NULL(); return;}
#define RETURN_LONG(l)                  { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d)                { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate)     { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING()           { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor)     { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE                    { RETVAL_FALSE; return; }
#define RETURN_TRUE                     { RETVAL_TRUE; return; }

再回頭看str_split裏的實現,咱們發現沒有使用RETURN_*相關的宏進行返回。這是怎麼回事呢?仔細看,發現使用array_init_size修改了return_value指針,咱們追蹤array_init_size代碼:

#define array_init_size(arg, size) _array_init((arg), (size) ZEND_FILE_LINE_CC)

繼續展開:

ZEND_API int _array_init(zval *arg, uint size ZEND_FILE_LINE_DC) /* {{{ */
{
    ALLOC_HASHTABLE_REL(Z_ARRVAL_P(arg));

    _zend_hash_init(Z_ARRVAL_P(arg), size, ZVAL_PTR_DTOR, 0 ZEND_FILE_LINE_RELAY_CC);
    Z_TYPE_P(arg) = IS_ARRAY;
    return SUCCESS;
}

原來array_init_size底層已經實現了RETURN_*的功能。

php_error_docref

php_error_docref是一個錯誤拋出函數。還有一個zend_error函數,它主要被Zend Engine使用,但也常常出如今擴展代碼中。

兩個函數都使用sprintf函數,好比格式化信息,所以錯誤信息能夠包含佔位符,那些佔位符會被後面的參數填充。下面有一個例子:

php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed to write %d bytes to %s", Z_STRLEN_PP(tmp), filename);

// %d is filled with Z_STRLEN_PP(tmp)
// %s is filled with filename

參考:

array_init_size

#define array_init_size(arg, size) _array_init((arg), (size) ZEND_FILE_LINE_CC)

初始化一個數組,指定初始化數組的元素個數。該函數定義在Zend_API.h裏。

代碼裏:

array_init_size(return_value, ((str_len - 1) / split_length) + 1);

初始化了一個數組,大小爲字符串分段長度:最終分爲幾部分,使用向上取整方法。

用向上取整的計算公式爲 : (a-1)/b+1 。

參考:

array_init

該函數與array_init_size用法類似,只是不用指定數組大小。該函數用於初始化一個空數組。

#define array_init(arg)         _array_init((arg), 0 ZEND_FILE_LINE_CC)

示例:

ZEND_FUNCTION(sample_array)
{
    array_init(return_value);
}

//return_value是zval*類型的,因此咱們直接對它調用array_init()函數便可,即把它初始化成了一個空數組。
增!

add_next_index_stringl

將數組初始化後,接下來就要向其添加元素了。

函數原型:

int add_next_index_stringl(zval *arg, const char *str, uint length, int duplicate)

該函數就是給指定數組增長一個元素,該元素是字符串類型,其中length參數指的是截取的str的長度。該函數是二進制安全的。

代碼裏屢次用到這個函數:

//參數指定長度大於字符串長度,不用分割了,直接返回字符串自己便可
if (split_length >= str_len) {
    add_next_index_stringl(return_value, str, str_len, 1);
    return;
}

//分段長度
n_reg_segments = str_len / split_length;
p = str;

//字符串指針p每次日後移動split_length長度
while (n_reg_segments-- > 0) {
    add_next_index_stringl(return_value, p, split_length, 1);
    p += split_length;
}

//當str_len / split_length不能整除的時候, str_len > split_length * n_reg_segments 
if (p != (str + str_len)) {
    add_next_index_stringl(return_value, p, (str + str_len - p), 1);
}

擴展閱讀:給數組添加元素

上面介紹的add_next_index_stringl函數是add_next_index_string的變種,l表示length。其實相似的還有不少。由於PHP語言中有多種類型的變量,因此也對應的有多種類型的add_assoc()add_index()add_next_index*()函數。如:

array_init(arrval);

add_assoc_long(zval *arrval, char *key, long lval);
add_index_long(zval *arrval, ulong idx, long lval);
add_next_index_long(zval *arrval, long lval);

這三個函數的第一個參數都要被操做的數組指針,而後是索引值,最後是變量,惟一不一樣的是add_next_index_long()函數的索引值是其本身計算出來的。

這三個函數分別在內部使用了zend_hash_update()zend_hash_index_update()zend_hash_next_index_insert()函數。

//add_assoc_*系列函數:
add_assoc_null(zval *aval, char *key);
add_assoc_bool(zval *aval, char *key, zend_bool bval);
add_assoc_long(zval *aval, char *key, long lval);
add_assoc_double(zval *aval, char *key, double dval);
add_assoc_string(zval *aval, char *key, char *strval, int dup);
add_assoc_stringl(zval *aval, char *key,char *strval, uint strlen, int dup);
add_assoc_zval(zval *aval, char *key, zval *value);

//備註:其實這些函數都是宏,都是對add_assoc_*_ex函數的封裝。

//add_index_*系列函數:
ZEND_API int add_index_long     (zval *arg, ulong idx, long n);
ZEND_API int add_index_null     (zval *arg, ulong idx           );
ZEND_API int add_index_bool     (zval *arg, ulong idx, int b    );
ZEND_API int add_index_resource (zval *arg, ulong idx, int r    );
ZEND_API int add_index_double   (zval *arg, ulong idx, double d);
ZEND_API int add_index_string   (zval *arg, ulong idx, const char *str, int duplicate);
ZEND_API int add_index_stringl  (zval *arg, ulong idx, const char *str, uint length, int duplicate);
ZEND_API int add_index_zval     (zval *arg, ulong index, zval *value);

//add_next_index_long函數:
ZEND_API int add_next_index_long        (zval *arg, long n  );
ZEND_API int add_next_index_null        (zval *arg          );
ZEND_API int add_next_index_bool        (zval *arg, int b   );
ZEND_API int add_next_index_resource    (zval *arg, int r   );
ZEND_API int add_next_index_double      (zval *arg, double d);
ZEND_API int add_next_index_string      (zval *arg, const char *str, int duplicate);
ZEND_API int add_next_index_stringl     (zval *arg, const char *str, uint length, int duplicate);
ZEND_API int add_next_index_zval        (zval *arg, zval *value);

總結:
上述這些函數都是給指定數組增長元素的。add_index_*add_assoc_*系列函數的第一個參數都要被操做的數組指針,而後是索引值,最後是變量;add_next_index_*系列函數無需指定索引值。

下面讓咱們經過一個例子來演示下它們的用法:

ZEND_FUNCTION(sample_array)
{
    zval *subarray;

    array_init(return_value);

    /* Add some scalars */
    add_assoc_long(return_value, "life", 42);
    add_index_bool(return_value, 123, 1);
    add_next_index_double(return_value, 3.1415926535);

    /* Toss in a static string, dup'd by PHP */
    add_next_index_string(return_value, "Foo", 1);

    /* Now a manually dup'd string */
    add_next_index_string(return_value, estrdup("Bar"), 0);

    /* Create a subarray */
    MAKE_STD_ZVAL(subarray);
    array_init(subarray);

    /* Populate it with some numbers */
    add_next_index_long(subarray, 1);
    add_next_index_long(subarray, 20);
    add_next_index_long(subarray, 300);

    /* Place the subarray in the parent */
    add_index_zval(return_value, 444, subarray);
}

這時若是咱們用戶端var_dump這個函數的返回值便會獲得:

<?php
var_dump(sample_array());

輸出:

array(6)
{
    ["life"]=> int(42)
    [123]=> bool(true)
    [124]=> float(3.1415926535)
    [125]=> string(3) "Foo"
    [126]=> string(3) "Bar"
    [444]=> array(3)
    {
        [0]=> int(1)
        [1]=> int(20)
        [2]=> int(300)
    }
}

參考:

在內核中操做PHP語言中數組 - PHP 擴展開發及內核應用相關內容 - 極客學院Wiki
http://wiki.jikexueyuan.com/project/extending-embedding-php/8.3.html

相關文章
相關標籤/搜索