0

0

深入懂得PHP内核(六)函数的定义、传参及返回值

php中文网

php中文网

发布时间:2016-06-13 12:28:37

|

964人浏览过

|

来源于php中文网

原创

深入理解PHP内核(六)函数的定义、传参及返回值

一、函数的定义

  用户函数的定义从function 关键字开始,如下

function foo($var) {    echo $var;}

  1、词法分析

  在Zend/zend_language_scanner.l中我们找到如下所示的代码:

"function" {    return T_FUNCTION;}

  它所表示的含义是function将会生成T_FUNCTION标记。在获取这个标记后,我们开始语法分析。

立即学习PHP免费学习笔记(深入)”;

  2、语法分析

  在Zend/zend_language_parser.y文件中找到函数的声明过程标记如下:

function:    T_FUNCTION { $$.u.opline_num = CG(zend_lineno); }; is_reference:        /* empty */ { $$.op_type = ZEND_RETURN_VAL; }    |   '&'         { $$.op_type = ZEND_RETURN_REF; }; unticked_function_declaration_statement:        function is_reference T_STRING {zend_do_begin_function_declaration(&$1, &$3, 0, $2.op_type, NULL TSRMLS_CC); }            '(' parameter_list ')' '{' inner_statement_list '}' {                zend_do_end_function_declaration(&$1 TSRMLS_CC); };

    关注点在function is_reference T_STRING,表示function关键字,是否引用,函数名

  T_FUNCTION标记只是用来定位函数的声明,表示这是一个函数,而更多的工作是与这个函数相关的东西,包括参数,返回值。

  3、生成中间代码

  语法解析后,我们看到所执行编译函数为zend_do_begin_function_declaration。在Zend/zend_complie.c文件找到其实现如下:

void zend_do_begin_function_declaration(znode *function_token, znode *function_name, int is_method, int return_reference, znode *fn_flags_znode TSRMLS_DC) /* {{{ */{    ...//省略    function_token->u.op_array = CG(active_op_array);    lcname = zend_str_tolower_dup(name, name_len);     orig_interactive = CG(interactive);    CG(interactive) = 0;    init_op_array(&op_array, ZEND_USER_FUNCTION, INITIAL_OP_ARRAY_SIZE TSRMLS_CC);    CG(interactive) = orig_interactive;      ...//省略     if (is_method) {        ...//省略,类方法 在后面的章节介绍?!?GH    } else {        zend_op *opline = get_next_op(CG(active_op_array) TSRMLS_CC);          opline->opcode = ZEND_DECLARE_FUNCTION;        opline->op1.op_type = IS_CONST;        build_runtime_defined_function_key(&opline->op1.u.constant, lcname,            name_len TSRMLS_CC);        opline->op2.op_type = IS_CONST;        opline->op2.u.constant.type = IS_STRING;        opline->op2.u.constant.value.str.val = lcname;        opline->op2.u.constant.value.str.len = name_len;        Z_SET_REFCOUNT(opline->op2.u.constant, 1);        opline->extended_value = ZEND_DECLARE_FUNCTION;        zend_hash_update(CG(function_table), opline->op1.u.constant.value.str.val,            opline->op1.u.constant.value.str.len, &op_array, sizeof(zend_op_array),             (void **) &CG(active_op_array));    } }/* }}} */

  生成的代码为ZEND_DECLARE_FUNCTION,根据这个中间的代码及操作数对应的op_type。我们可以找到中间代码的执行函数为ZEND_DECLARE_FUNCTION_SPEC_HANDLER。

    在生成中间代码的时候,可以看到已经统一了函数名全部为小写,表示函数的名称不是区  分大小写的。

  为验证这个实现,我们看一段代码

function T() {    echo 1;} function t() {    echo 2;}

  执行代码会报错Fatal error: Cannot redeclare t() (previously declared in ...)

  表示对于PHP来说T和t是同一个函数名,校验函数名是否重复,这个过程是在哪进行的呢?

  4、执行中间代码

  在Zend/zend_vm_execute.h文件中找到ZEND_DECLARE_FUNCTION中间代码对应的执行函数:ZEND_DECLARE_FUNCTION_SPEC_HANDLER。此函数只调用了函数do_bind_function。其调用代码为:

do_bind_function(EX(opline), EG(function_table), 0);

  在这个函数中将EX(opline)所指向的函数添加到EG(function_table)中,并判断是否已经存在相同名字的函数,如果存在则报错,EG(function_table)用来存放执行过程中全部的函数信息,相当于函数的注册表。它的结构是一个HashTable,所以在do_bind_function函数中添加新的函数使用的是HashTable的操作函数zend_hash_add

 

二、函数的参数

  函数的定义只是一个将函数名注册到函数列表的过程。

  1、用户自定义函数的参数

  我们知道对于函数的参数检查是通过zend_do_receive_arg函数来实现的,在此函数中对于参数的关键代码如下:

CG(active_op_array)->arg_info = erealloc(CG(active_op_array)->arg_info,        sizeof(zend_arg_info)*(CG(active_op_array)->num_args));cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];cur_arg_info->name = estrndup(varname->u.constant.value.str.val,        varname->u.constant.value.str.len);cur_arg_info->name_len = varname->u.constant.value.str.len;cur_arg_info->array_type_hint = 0;cur_arg_info->allow_null = 1;cur_arg_info->pass_by_reference = pass_by_reference;cur_arg_info->class_name = NULL;cur_arg_info->class_name_len = 0;

  整个参数的传递是通过给中间代码的arg_info字段执行赋值操作完成。关键点是在arg_info字段,arg_info字段的结构如下:

Booltool
Booltool

常用AI图片图像处理工具箱

下载
typedef struct _zend_arg_info {    const char *name;   /*参数的名称*/    zend_uint name_len;     /*参数名称的长度*/    const char *class_name; /* 类名*/     zend_uint class_name_len;   /*类名长度*/    zend_bool array_type_hint;  /*数组类型提示*/    zend_bool allow_null;   /*是否允许为NULL?*/    zend_bool pass_by_reference;    /*是否引用传递*/    zend_bool return_reference;     int required_num_args;  } zend_arg_info;

  参数的值传递和参数传递的区别是通过pass_by_reference参数在生成中间代码时实现的。

  对于参数的个数,中间代码中包含的arg_nums字段在每次执行**zend_do_receive_argxx时都会加1.如下代码:

CG(active_op_array)->num_args++;

  并且当前参数的索引为?CG(active_op_array)->num_args-1.如下代码:

cur_arg_info = &CG(active_op_array)->arg_info[CG(active_op_array)->num_args-1];

  以上的分析是针对函数定义时的参数设置,这些参数是固定的。而在实际编写程序时可能我们会用到可变参数。此时我们会用到函数func_num_args和func_get_args。它们是以内部函数存在。于是在Zend\zend_builtin_functions.c文件中找到这两个函数的实现。我们首先来看func_num_args函数的实现,其代码如下:

/* {{{ proto int func_num_args(void)   Get the number of arguments that were passed to the function */ZEND_FUNCTION(func_num_args){    zend_execute_data *ex = EG(current_execute_data)->prev_execute_data;     if (ex && ex->function_state.arguments) {        RETURN_LONG((long)(zend_uintptr_t)*(ex->function_state.arguments));    } else {        zend_error(E_WARNING,"func_num_args():  Called from the global scope - no function context");        RETURN_LONG(-1);    }}/* }}} */

  在存在ex->function_state.arguments的情况下,及函数调用时,返回ex->function_state.arguments转化后的值,否则显示错误并返回-1。这里最关键的一点是EG(current_execute_data)。这个变量存放的是当前执行程序或函数的数据,此时我们需要取前一个执行程序的数据,为什么呢?因为这个函数的调用是在进入函数后执行的。函数的相关数据等都在之前执行过程中,于是调用的是:

zend_execute_data *ex = EG(current_execute_data)->prev_execute_data;

 

  2、内部函数的参数

  以常见的count函数为例,其参数处理部分的代码如下:

/* {{{ proto int count(mixed var [, int mode])   Count the number of elements in a variable (usually an array) */PHP_FUNCTION(count){    zval *array;    long mode = COUNT_NORMAL;     if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z|l",         &array, &mode) == FAILURE) {        return;    }    ... //省略}

  这里包括了两个操作:一个是取参数的个数,一个是解析参数列表。

  (1)取参数的个数

  取参数的个数是通过ZEND_NUM_ARGS()宏来实现的,其定义如下:

#define ZEND_NUM_ARGS()     (ht)

  ht是在Zend/zend.h文件中定义的宏INTERNAL_FUNCTION_PARAMETERS中的ht,如下

#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value,zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

  (2)解析参数列表

  PHP内部函数在解析参数时使用的是zend_parse_parameters。它可以大大简化参数的接收处理工作,虽然它在处理可变参数时还有点弱。

  其声明如下:

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参数值的变量的指针。

  zend_parse_parameters()在解析参数的同时户尽可能的转换参数类型,这样就可以确保我们总是能得到所期望的类型的变量

 

  3、函数的返回值

  PHP中函数都有返回值,没return返回null

  (1)return语句

  从Zend/zend_language_parser.y文件中可以确认其生成中间代码调用的是zend_do_return函数。

void zend_do_return(znode *expr, int do_end_vparse TSRMLS_DC) /* {{{ */{    zend_op *opline;    int start_op_number, end_op_number; if (do_end_vparse) {        if (CG(active_op_array)->return_reference                && !zend_is_function_or_method_call(expr)) {            zend_do_end_variable_parse(expr, BP_VAR_W, 0 TSRMLS_CC);/* 处理返回引用 */        } else {            zend_do_end_variable_parse(expr, BP_VAR_R, 0 TSRMLS_CC);/* 处理常规变量返回 */        }    }    ...// 省略,取其他中间代码操作     opline->opcode = ZEND_RETURN;     if (expr) {        opline->op1 = *expr;         if (do_end_vparse && zend_is_function_or_method_call(expr)) {            opline->extended_value = ZEND_RETURNS_FUNCTION;        }    } else {        opline->op1.op_type = IS_CONST;        INIT_ZVAL(opline->op1.u.constant);    }     SET_UNUSED(opline->op2);}/* }}} */

  生成中间代码为ZEND_RETURN。第一个操作数的类型在返回值为可用的表达式时,其类型为表达式的操作类型,否则类型为IS_CONST。这在后续计算执行中间代码函数时有用到。根据操作数的不同,ZEND_RETURN中间代码会执行ZEND_RETURN_SPEC_CONST_HANDLER,ZEND_RETURN_SPEC_TMP_HANDLER或ZEND_RETURN_SPEC_TMP_HANDLER。这三个函数的执行流程基本类似,包括对一些错误的处理。这里我们以ZEND_RETURN_SPEC_CONST_HANDLER为例说明函数返回值的执行过程:

static int ZEND_FASTCALL  ZEND_RETURN_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS){    zend_op *opline = EX(opline);    zval *retval_ptr;    zval **retval_ptr_ptr;      if (EG(active_op_array)->return_reference == ZEND_RETURN_REF) {         //  ?ǔ?sÁ\[email protected]Á??        if (IS_CONST == IS_CONST || IS_CONST == IS_TMP_VAR) {               /* Not supposed to happen, but we'll allow it */            zend_error(E_NOTICE, "Only variable references \                should be returned by reference");            goto return_by_value;        }         retval_ptr_ptr = NULL;  //  ?ǔ?         if (IS_CONST == IS_VAR && !retval_ptr_ptr) {            zend_error_noreturn(E_ERROR, "Cannot return string offsets by reference");        } if (IS_CONST == IS_VAR && !Z_ISREF_PP(retval_ptr_ptr)) {            if (opline->extended_value == ZEND_RETURNS_FUNCTION &&                EX_T(opline->op1.u.var).var.fcall_returned_reference) {            } else if (EX_T(opline->op1.u.var).var.ptr_ptr ==                    &EX_T(opline->op1.u.var).var.ptr) {                if (IS_CONST == IS_VAR && !0) {                      /* undo the effect of get_zval_ptr_ptr() */                    PZVAL_LOCK(*retval_ptr_ptr);                }                zend_error(E_NOTICE, "Only variable references \                 should be returned by reference");                goto return_by_value;            }        }         if (EG(return_value_ptr_ptr)) { //  ?ǔ?s            SEPARATE_ZVAL_TO_MAKE_IS_REF(retval_ptr_ptr);   //  is_ref__gc???1            Z_ADDREF_PP(retval_ptr_ptr);    //  refcount__gcŒ?×1             (*EG(return_value_ptr_ptr)) = (*retval_ptr_ptr);        }    } else {return_by_value:         retval_ptr = &opline->op1.u.constant;         if (!EG(return_value_ptr_ptr)) {            if (IS_CONST == IS_TMP_VAR) {             }        } else if (!0) { /* Not a temp var */            if (IS_CONST == IS_CONST ||                EG(active_op_array)->return_reference == ZEND_RETURN_REF ||                (PZVAL_IS_REF(retval_ptr) && Z_REFCOUNT_P(retval_ptr) > 0)) {                zval *ret;                 ALLOC_ZVAL(ret);                INIT_PZVAL_COPY(ret, retval_ptr);   //  ?????ǔ?                 zval_copy_ctor(ret);                *EG(return_value_ptr_ptr) = ret;            } else {                *EG(return_value_ptr_ptr) = retval_ptr; //  ?6??                Z_ADDREF_P(retval_ptr);            }        } else {            zval *ret;             ALLOC_ZVAL(ret);            INIT_PZVAL_COPY(ret, retval_ptr);    //  ?????ǔ?             *EG(return_value_ptr_ptr) = ret;            }    }     return zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU);   //  ?ǔ????}

  函数的返回值在程序执行时存储在*EG(return_value_ptr_ptr)。ZEND内核对值返回和引用返回作了区别,并且在此基础上对常量,临时变量和其他类型的变量在返回时作了不同的处理。在return执行完之后,ZEND内核通过调用zend_leave_helper_SPEC函数,清除函数内部使用的变量等。这也是ZEND内核自动给函数加上NULL返回的原因之一。

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
Python 序列化
Python 序列化

本专题整合了python序列化、反序列化相关内容,阅读专题下面的文章了解更多详细内容。

0

2026.02.02

AO3官网入口与中文阅读设置 AO3网页版使用与访问
AO3官网入口与中文阅读设置 AO3网页版使用与访问

本专题围绕 Archive of Our Own(AO3)官网入口展开,系统整理 AO3 最新可用官网地址、网页版访问方式、正确打开链接的方法,并详细讲解 AO3 中文界面设置、阅读语言切换及基础使用流程,帮助用户稳定访问 AO3 官网,高效完成中文阅读与作品浏览。

91

2026.02.02

主流快递单号查询入口 实时物流进度一站式追踪专题
主流快递单号查询入口 实时物流进度一站式追踪专题

本专题聚合极兔快递、京东快递、中通快递、圆通快递、韵达快递等主流物流平台的单号查询与运单追踪内容,重点解决单号查询、手机号查物流、官网入口直达、包裹进度实时追踪等高频问题,帮助用户快速获取最新物流状态,提升查件效率与使用体验。

27

2026.02.02

Golang WebAssembly(WASM)开发入门
Golang WebAssembly(WASM)开发入门

本专题系统讲解 Golang 在 WebAssembly(WASM)开发中的实践方法,涵盖 WASM 基础原理、Go 编译到 WASM 的流程、与 JavaScript 的交互方式、性能与体积优化,以及典型应用场景(如前端计算、跨平台模块)。帮助开发者掌握 Go 在新一代 Web 技术栈中的应用能力。

11

2026.02.02

PHP Swoole 高性能服务开发
PHP Swoole 高性能服务开发

本专题聚焦 PHP Swoole 扩展在高性能服务端开发中的应用,系统讲解协程模型、异步IO、TCP/HTTP/WebSocket服务器、进程与任务管理、常驻内存架构设计。通过实战案例,帮助开发者掌握 使用 PHP 构建高并发、低延迟服务端应用的工程化能力。

5

2026.02.02

Java JNI 与本地代码交互实战
Java JNI 与本地代码交互实战

本专题系统讲解 Java 通过 JNI 调用 C/C++ 本地代码的核心机制,涵盖 JNI 基本原理、数据类型映射、内存管理、异常处理、性能优化策略以及典型应用场景(如高性能计算、底层库封装)。通过实战示例,帮助开发者掌握 Java 与本地代码混合开发的完整流程。

5

2026.02.02

go语言 注释编码
go语言 注释编码

本专题整合了go语言注释、注释规范等等内容,阅读专题下面的文章了解更多详细内容。

62

2026.01.31

go语言 math包
go语言 math包

本专题整合了go语言math包相关内容,阅读专题下面的文章了解更多详细内容。

55

2026.01.31

go语言输入函数
go语言输入函数

本专题整合了go语言输入相关教程内容,阅读专题下面的文章了解更多详细内容。

27

2026.01.31

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
zend框架2视频教程
zend框架2视频教程

共79课时 | 14万人学习

前端系列快速入门课程
前端系列快速入门课程

共4课时 | 0.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号