Skip to content

Latest commit

 

History

History
199 lines (162 loc) · 8.08 KB

1-3-3:数据结构-Function.md

File metadata and controls

199 lines (162 loc) · 8.08 KB

介绍

在PHP中,函数分为俩种: 一种是zend_internal_function, 这种函数是由扩展或者Zend/PHP内核提供的,用’C/C++’编写的,可以直接执行的函数. 另外一种是zend_user_function, 这种函数呢,就是我们经常在见的,用户在PHP脚本中定义的函数,这种函数最终会被ZE翻译成opcode array来执行

zval : zend_function func 类型成员 EG(function_table)是一个哈希表,记录的就是PHP中所有的函数.

zend_internal_function,zend_function,zend_op_array这三种结构在一定程序上存在公共的元素, 于是这些元素以联合体的形式共享内存,并且在执行过程中对于一个函数,这三种结构对应的字段在值上都是一样的, 于是可以在一些结构间发生完美的强制类型转换.zend_op_array与zend_internal_function结构的起始位置都有common中的几个成员,common可以看作是op_array、internal_function的header,不管是什么哪种函数都可以通过zend_function.common.xx快速访问,zend_function可以与zend_op_array互换,zend_function可以与zend_internal_function互换,但是一个zend_op_array结构转换成zend_function是不能再次转变成zend_internal_function结构的,反之亦然.

结构

//zend_compile.h
union _zend_function {
	zend_uchar type;	/* 函数类型 */
	uint32_t   quick_arg_flags;
	struct {
		zend_uchar type;  /* never used */
		zend_uchar arg_flags[3];        /* bitset of arg_info.pass_by_reference */
		uint32_t fn_flags;              //作为方法时的访问类型等,如ZEND_ACC_STATIC等 
		zend_string *function_name;     //函数名称
		zend_class_entry *scope;        //成员方法所属类,面向对象实现中用到
		union _zend_function *prototype;//函数原型
		uint32_t num_args;              //参数数量
		uint32_t required_num_args;     //必传参数数量
		zend_arg_info *arg_info;        //参数信息
	} common;
	zend_op_array op_array;             //函数实际编译为普通的zend_op_array
	zend_internal_function internal_function;
};
//内部函数结构
typedef struct _zend_internal_function {
    /* Common elements */
    zend_uchar type;
    zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
    uint32_t fn_flags;
    zend_string* function_name;
    zend_class_entry *scope;
    zend_function *prototype;
    uint32_t num_args;
    uint32_t required_num_args;
    zend_internal_arg_info *arg_info;
    /* END of common elements */

    void (*handler)(INTERNAL_FUNCTION_PARAMETERS); //函数指针,展开:void (*handler)(zend_execute_data *execute_data, zval *return_value)
    struct _zend_module_entry *module;
    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
} zend_internal_function;
//用户自定义函数结构
struct _zend_op_array {
    /* Common elements common是普通函数或类成员方法对应的opcodes快速访问时使用的字段*/
	zend_uchar type;
	zend_uchar arg_flags[3]; /* bitset of arg_info.pass_by_reference */
	uint32_t fn_flags;
	zend_string *function_name;
	zend_class_entry *scope;
	zend_function *prototype;
	uint32_t num_args;
	uint32_t required_num_args;
	zend_arg_info *arg_info;
	/* END of common elements */

	uint32_t *refcount;

	uint32_t last;      
    zend_op *opcodes;   //opcode指令数组   
    int last_var;       //PHP代码里定义的变量数:op_type为IS_CV的变量,不含IS_TMP_VAR、IS_VAR的,编译前0,然后发现一个新变量这个值就加1
    uint32_t T;         //临时变量数:op_type为IS_TMP_VAR、IS_VAR的变量    
    zend_string **vars; //这个数组在ast编译期间配合last_var用来确定各个变量的编号,非常重要的一步操作//PHP变量名数组
    ...
    HashTable *static_variables;    //静态变量符号表:通过static声明的
    ...
    int last_literal;   //字面量数量    
    zval *literals;     //字面量(常量)数组,这些都是在PHP代码定义的一些值
    int  cache_size;    //运行时缓存数组大小    
    void **run_time_cache;  //运行时缓存,主要用于缓存一些znode_op以便于快速获取数据,后面单独介绍这个机制

    void *reserved[ZEND_MAX_RESERVED_RESOURCES];
};
//函数类型
#define ZEND_INTERNAL_FUNCTION              1   //内置的函数
#define ZEND_USER_FUNCTION                  2   //用户函数
#define ZEND_OVERLOADED_FUNCTION            3   //对象中__call相关
#define ZEND_EVAL_CODE                      4   //eval code
#define ZEND_OVERLOADED_FUNCTION_TEMPORARY  5   //对象中__call相关

用户自定义函数

PHP在编译阶段将用户自定义的函数编译为独立的opcodes,保存在EG(function_table)中,调用时重新分配新的zend_execute_data(相当于运行栈),然后执行函数的opcodes,调用完再还原到旧的zend_execute_data,继续执行,关于zend引擎execute阶段后面会详细分析. zend_function的结构中的op_array存储了该函数中所有的操作,当函数被调用时,ZE就会将这个op_array中的opline一条条顺次执行, 并将最后的返回值返回. 从VLD扩展中查看的关于函数的信息可以看出,函数的定义和执行是分开的,一个函数可以作为一个独立的运行单元而存在.

函数参数

参数名称也在zend_op_array.vars中,编号首先是从参数开始的,所以按照参数顺序其编号依次为0、1、2... 参数的其它信息通过zend_arg_info结构记录:

typedef struct _zend_arg_info {
    zend_string *name;      //参数名
    zend_string *class_name;//类名
    zend_uchar type_hint;   //显式声明的参数类型,比如(array $param_1)
    zend_uchar pass_by_reference;   //是否引用传参,参数前加&的这个值就是1
    zend_bool allow_null;   //是否允许为NULL,注意:这个值并不是用来表示参数是否为必传的
    zend_bool is_variadic;  //是否为可变参数,即...用法,与golang的用法相同,5.6以上新增的一个用法:function my_func($a, ...$b){...}
} zend_arg_info;

每个参数都有一个上面的结构,所有参数的结构保存在zend_op_array.arg_info数组中,这里有一个地方需要注意:zend_op_array->arg_info数组保存的并不全是输入参数,如果函数声明了返回值类型则也会为它创建一个zend_arg_info,这个结构在arg_info数组的第一个位置,这种情况下zend_op_array->arg_info指向的实际是数组的第二个位置,返回值的结构通过zend_op_array->arg_info[-1]读取.

编译过程

zend引擎编译课程再讲解

内部函数

内部函数的定义非常简单,我们只需要创建一个普通的C函数,然后创建一个zend_internal_function结构添加到 EG(function_table)

解析函数参数

ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, char *type_spec, ...) 第一个参数num_args表明表示想要接收的参数个数,我们经常使用ZEND_NUM_ARGS() 来表示对传入的参数“有多少要多少”. 第二参数应该总是宏 TSRMLS_CC . 第三个参数 type_spec 是一个字符串,用来指定我们所期待接收的各个参数的类型,有点类似于 printf 中指定输出格式的那个格式化字符串. 剩下的参数就是我们用来接收PHP参数值的变量的指针.

PHP自带函数讲解

// ext/standard/string.c : line 2744
static void php_ucfirst(char *str)
{
	register char *r;
	r = str;
	*r = toupper((unsigned char) *r);
}
PHP_FUNCTION(ucfirst)
{
	zend_string *str;

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_STR(str)
	ZEND_PARSE_PARAMETERS_END();

	if (!ZSTR_LEN(str)) {
		RETURN_EMPTY_STRING();
	}

	ZVAL_STRINGL(return_value, ZSTR_VAL(str), ZSTR_LEN(str));
	php_ucfirst(Z_STRVAL_P(return_value));
}
// ext/standard/php_string.h
PHP_FUNCTION(ucfirst);

注册新函数

在扩展课程再讲解

函数注册过程

测试

<?php
/*
//列出所有函数
function test(){
	print_r(get_defined_functions());
}
test();
*/
/* 调用用户定义函数
function test(){}
test();
 */
/* 调用内部函数 */
strtoupper('test');

函数

分类

zval function

结构

内部函数

扩展结构

opcode

编译过程示例

参考资料:

http://www.laruence.com/
https://github.com/pangudashu/php7-internal/
http://www.php-internals.com/book