PHP源码阅读(一):str_split函数

时间:2022-10-20 09:45:32

注:源码版本:php5.6.33。

函数简介

str_split 原型:

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行。这里我贴出来:

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

首先看参数解析部分:

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

1、第一个参数我们使用默认值。下面是原因:

传递给 zend_parse_parameters() 的第一个参数是用户实际传递到函数的参数数量。此数值做为 ht 参数传给函数,但就像上面讨论的那样,应使用做为实现细节的 ZEND_NUM_ARGS()。为了与 PHP 的线程隔离、线程安全资源管理器兼容,还要用 TSRMLS_CC 传递线程上下文。与其他函数不同,它不能是最后的参数,因为在 zend_parse_parameters 内要求有不定数量的参数——依赖于要读取的用户参数的数量。

2、第二个参数定义所要求的参数。

每个参数都由字符串中的一个字符表示其类型。 如果希望一个字符串参数,则在此类型说明只不过是个 "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。

3、最后一个参数是传递一个或多个指针给要填充变量值的 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

PHP源码阅读(一):str_split函数的更多相关文章

  1. linux源码阅读笔记 fork函数

    在阅读源码的过程中,发现找不到fork函数的定义.后来在linux/init/main.c中找到了这样一条语句 static inline _syscall0(int,fork) 原来这里就是fork ...

  2. [PHP源码阅读]number&lowbar;format函数

    上次讲到PHP是如何解析大整数的,一笔带过了number_format的处理,再详细阅读该函数的源码,以下是小分析. 函数原型 string number_format ( float $number ...

  3. linux源码阅读笔记 asm函数

    在linux源码中经常遇到__asm__函数.它其实是函数asm的宏定义 #define __asm__ asm,asm函数让系统执行汇编语句. __asm__常常与__volatile__一起出现. ...

  4. &lbrack;PHP源码阅读&rsqb;explode和implode函数

    explode和implode函数主要用作字符串和数组间转换的操作,比如获取一段参数后根据某个字符分割字符串,或者将一个数组的结果使用一个字符合并成一个字符串输出.在PHP中经常会用到这两个函数,因此 ...

  5. CI框架源码阅读笔记3 全局函数Common&period;php

    从本篇开始,将深入CI框架的内部,一步步去探索这个框架的实现.结构和设计. Common.php文件定义了一系列的全局函数(一般来说,全局函数具有最高的加载优先权,因此大多数的框架中BootStrap ...

  6. 3 EventTime 事件时间类和TimeNow函数——Live555源码阅读&lpar;一&rpar;基本组件类

    这是Live555源码阅读的第一部分,包括了时间类,延时队列类,处理程序描述类,哈希表类这四个大类. 这里是时间相关类的第三个部分,也是最后一个部分. EventTime 事件时间类 这个类和Dela ...

  7. PHP源码阅读笔记一(explode和implode函数分析)

    PHP源码阅读笔记一一.explode和implode函数array explode ( string separator, string string [, int limit] )此函数返回由字符 ...

  8. &lbrack;PHP源码阅读&rsqb;strtolower和strtoupper函数

    字符串的操作函数中,字符串的大小写转换也算是比较常用的函数,其底层实现也比较简单,下面来一探究竟. 我在github上有对PHP源码更详细的注解.感兴趣的可以围观一下,给个star.PHP5.4源码注 ...

  9. 【原】FMDB源码阅读(三)

    [原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...

  10. 【原】FMDB源码阅读(二)

    [原]FMDB源码阅读(二) 本文转载请注明出处 -- polobymulberry-博客园 1. 前言 上一篇只是简单地过了一下FMDB一个简单例子的基本流程,并没有涉及到FMDB的所有方方面面,比 ...

随机推荐

  1. JAVA语言搭建白盒静态代码、黑盒网站插件式自动化安全审计平台

    近期打算做一个插件化的白盒静态代码安全审计自动化平台和黑盒网站安全审计自动化平台.现在开源或半开源做黑盒网站安全扫描的平台,大多是基于python脚本,安全人员贡献python脚本插件增强平台功能.对 ...

  2. haligong2016

    A 采用递推的方法,由于要到达棋盘上的一个点,只能从左边或者上边过来,根据加法原则,到达某一点的路径数目,就等于到达其相邻的上点和左点的路径数目的总和.所有海盗能达到的点将其路径数置为0即可. #in ...

  3. VMware vCenter Server 6&period;5&period;0 U1

    VMware vCenter Server 6.5.0 U1gName: VMware-VCSA-all-6.5.0-8024368.iso Release Date: 2018-03-20 Buil ...

  4. ValueError&colon; too many values to unpack

    Error msg: 执行: python manage,py makemigrations 报错:Value: too many values to unpack 问题: django第一次数据库迁 ...

  5. Saltstack 集中化管理平台安装

    Saltstack的简介 SaltStack(http://www.saltstack.com/)是一个服务器基础架构集中化管理平台,具备配置管理.远程执行.监控等功能,一般可以理解为简化版的pupp ...

  6. 洛谷 P4112 &lbrack;HEOI2015&rsqb;最短不公共子串 解题报告

    P4112 [HEOI2015]最短不公共子串 题目描述 在虐各种最长公共子串.子序列的题虐的不耐烦了之后,你决定反其道而行之. 一个串的"子串"指的是它的连续的一段,例如bcd是 ...

  7. 表格细边框的两种CSS实现方法

    在网页制作中,细边框这个制作方法是必不可少的.这里介绍2种常见的表格细边框制作方法,均通过XHTML验证. <!DOCTYPE html PUBLIC "-//W3C//DTD XHT ...

  8. asp&period;net——Base64加密解密

    /// <summary> /// 实现Base64加密解密 /// </summary> public sealed class Base64 { /// <summa ...

  9. C&num;关于递归等等

    递归的例子1 计算1到100相加的值 public partial class Default4 : System.Web.UI.Page{    protected void Page_Load(o ...

  10. linux 修改openfiles

    使用ulimit -a 可以查看当前系统的所有限制值,使用ulimit -n 可以查看当前的最大打开文件数. 新装的linux默认只有1024,当作负载较大的服务器时,很容易遇到error: too ...