php函数的实现
1.函数 汇编中函数对应的是一组独立的汇编指令,然后通过call指令实现函数的调用。PHP编译的opcode数组,与汇编指令对应。
PHP用户自定义函数的实现就是将函数编译为独立的opcode数组,调用时分配独立的执行栈依次执行opcode,所以自定义函数对于zend而言并没有什么特别之处,只是将opcode进行了打包封装,PHP脚本中函数之外的指令,整个可以认为是一个函数(或者理解为main函数更直观)。
2.函数的存储结构
typedef union _zend_function zend_function; //zend_compile.h
union _zend_function {
zend_uchar type; /* MUST be the first element of this struct! */ struct {
zend_uchar type; /* never used */
zend_uchar arg_flags[]; /* bitset of arg_info.pass_by_reference */
uint32_t fn_flags;
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;
};
这是一个union,PHP中函数除了用户自定义函数还有一种:内部函数,内部函数是通过扩展或者内核提供的C函数,比如time、array系列等等。内部函数主要用到internal_function
,而用户自定义函数编译完就是一个普通的opcode数组,用的是op_array
(注意:op_array、internal_function是嵌入的两个结构,而不是一个单独的指针)
PHP脚本的生命周期中有一个非常重要的值executor_globals(
,类型是EG
宏实际就是对executor_globals
的操作)struct _zend_executor_globals
,它记录着PHP生命周期中所有的数据,如果你写过PHP扩展一定用到过。
EG(function_table)
是一个哈希表,记录的就是PHP中所有的函数。PHP在编译阶段将用户自定义的函数编译为独立的opcodes,保存在EG(function_table)
中。调用时重新分配新的zend_execute_data(相当于运行栈),然后执行函数的opcodes,调用完再还原到旧的zend_execute_data
,继续执行。
3.函数的参数
函数参数在实现上与函数内的局部变量实际是一样的,编译的时候会有一个单独的 编号 ,参数名称保存在zend_op_array.vars中,编号首先是从参数开始的,所以按照参数顺序其编号依次为0、1、2...(转化为相对内存偏移量就是96、112、128...),函数调用时首先会在调用位置将参数的value复制到各参数各自的位置。
参数还有一个存储参数信息的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;
4.函数的编译
PHP代码的编译过程,主要是PHP->AST->Opcodes的转化,函数其实就是将一组PHP代码编译为单独的opcodes,函数的调用就是不同opcodes间的切换,所以函数的编译过程与普通PHP代码基本一致。
过程:
a.保存当前正在编译的zend_op_array,新分配一个结构,因为每个函数、include的文件都对应独立的一个zend_op_array,通过CG(active_op_array)记录当前编译所属zend_op_array,所以开始编译函数时就需要将这个值保存下来,等到函数编译完成再还原回去,在当前zend_op_array(不是新生成的函数那个)生成一条 ZEND_DECLARE_FUNCTION 的opcode,也就是函数声明操作。
b.编译参数列表,完整的参数会有三个组成:参数类型(可选)、参数名、默认值(可选),这三部分分别保存在参数节点的三个child节点中,编译参数的过程有两个关键操作:为每个参数编号、每个参数生成一条opcode
c.编译函数内部语法,这个跟普通PHP代码编译过程无异
d.将IS_CONST、IS_VAR、IS_TMP_VAR类型的操作数、返回值转化为内存偏移量,设置各指令的处理handler
最终生成两个zend_op_array:
PHP在逐行编译时发现一个function则生成一条ZEND_DECLARE_FUNCTION的opcode,然后调到函数中编译函数,编译完再跳回去继续下面的编译,这里多次提到ZEND_DECLARE_FUNCTION这个opcode是因为在函数编译结束后还有一个重要操作:zend_do_early_binding(),核心工作就是 将function、class加到CG(function_table)、CG(class_table)中 ,加入成功了就直接把 ZEND_DECLARE_FUNCTION 这条opcode干掉了,加入失败的话则保留,这个相当于 有一部分opcode在『编译时』提前执行了 ,这也是为什么PHP中可以先调用函数再声明函数的原因。
5.匿名函数 匿名函数(Anonymous functions),也叫闭包函数(closures,允许临时创建一个没有指定名称的函数。最经常用作回调函数(callback)参数的值。当然,也有其它应用的情况。
6.内部函数 内部函数指的是由内核、扩展提供的C语言编写的function,这类函数不需要经历opcode的编译过程,所以效率上要高于PHP用户自定义的函数,调用时与普通的C程序没有差异。
函数的结构zend_function为union,其中其中internal_function
就是内部函数
//zend_complie.h
typedef struct _zend_internal_function {
/* Common elements */
zend_uchar type;
zend_uchar arg_flags[]; /* 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;
zend_internal_function
头部是一个与zend_op_array
完全相同的common结构。内部函数与用户自定义函数冲突,用户无法在PHP代码中覆盖内部函数,执行PHP脚本时会提示error错误。
内部函数的定义,我们只需要创建一个普通的C函数,然后创建一个zend_internal_function
结构添加到 EG(function_table) (也可能是CG(function_table),取决于在哪一阶段注册)中即可使用,内部函数 通常 情况下是在php_module_startup阶段注册的,通常是按照标准的扩展定义内部函数。
php函数的实现的更多相关文章
- Python 小而美的函数
python提供了一些有趣且实用的函数,如any all zip,这些函数能够大幅简化我们得代码,可以更优雅的处理可迭代的对象,同时使用的时候也得注意一些情况 any any(iterable) ...
- 探究javascript对象和数组的异同,及函数变量缓存技巧
javascript中最经典也最受非议的一句话就是:javascript中一切皆是对象.这篇重点要提到的,就是任何jser都不陌生的Object和Array. 有段时间曾经很诧异,到底两种数据类型用来 ...
- JavaScript权威指南 - 函数
函数本身就是一段JavaScript代码,定义一次但可能被调用任意次.如果函数挂载在一个对象上,作为对象的一个属性,通常这种函数被称作对象的方法.用于初始化一个新创建的对象的函数被称作构造函数. 相对 ...
- C++对C的函数拓展
一,内联函数 1.内联函数的概念 C++中的const常量可以用来代替宏常数的定义,例如:用const int a = 10来替换# define a 10.那么C++中是否有什么解决方案来替代宏代码 ...
- 菜鸟Python学习笔记第一天:关于一些函数库的使用
2017年1月3日 星期二 大一学习一门新的计算机语言真的很难,有时候连函数拼写出错查错都能查半天,没办法,谁让我英语太渣. 关于计算机语言的学习我想还是从C语言学习开始为好,Python有很多语言的 ...
- javascript中的this与函数讲解
前言 javascript中没有块级作用域(es6以前),javascript中作用域分为函数作用域和全局作用域.并且,大家可以认为全局作用域其实就是Window函数的函数作用域,我们编写的js代码, ...
- 复杂的 Hash 函数组合有意义吗?
很久以前看到一篇文章,讲某个大网站储存用户口令时,会经过十分复杂的处理.怎么个复杂记不得了,大概就是先 Hash,结果加上一些特殊字符再 Hash,结果再加上些字符.再倒序.再怎么怎么的.再 Hash ...
- JS核心系列:浅谈函数的作用域
一.作用域(scope) 所谓作用域就是:变量在声明它们的函数体以及这个函数体嵌套的任意函数体内都是有定义的. function scope(){ var foo = "global&quo ...
- C++中的时间函数
C++获取时间函数众多,何时该用什么函数,拿到的是什么时间?该怎么用?很多人都会混淆. 本文是本人经历了几款游戏客户端和服务器开发后,对游戏中时间获取的一点总结. 最早学习游戏客户端时,为了获取最精确 ...
- Python高手之路【四】python函数装饰器
def outer(func): def inner(): print('hello') print('hello') print('hello') r = func() print('end') p ...
随机推荐
- FormShortCut MainForm 和 二级FORM
发现,主FORM 定义的快捷键,在二级FORM里也有效. 反过来,就无效. 这样的话,就要考虑 快捷键的冲突问题 了,本来以为不同的FORM 是独立的. http://codeverge.com/em ...
- 驱动程序多线程 PsCreateSystemThread
内核函数PsCreateSystemThread负责创建新线程.该函数可以创建两种线程,一种是用户线程,它属于当前进程中的线程.另一种是系统线程,系统线程不属于当前用户进程,而是属于系统进程,一般PI ...
- DevExpress v17.2新版亮点—WPF篇(四)
DevExpress年终击穿底价,单套授权低至67折!仅剩最后6天!查看详情>>> 用户界面套包DevExpress v17.2终于正式发布,本站将以连载的形式为大家介绍各版本新增内 ...
- Femtocell家庭基站通信截获、伪造任意短信漏洞
阿里移动安全团队与中国泰尔实验室无线技术部的通信专家们一起,联合对国内运营商某型Femtocell基站进行了安全分析,发现多枚重大漏洞,可导致用户的短信.通话.数据流量被窃听.恶意攻击者可以在免费申领 ...
- WebGL编程指南案例解析之加载纹理(贴图)
var vShader = ` attribute vec4 a_Position; attribute vec2 a_TexCoord; varying vec2 v_TexCoord; void ...
- tensorflow中的参数初始化方法
1. 初始化为常量 tf中使用tf.constant_initializer(value)类生成一个初始值为常量value的tensor对象. constant_initializer类的构造函数定义 ...
- Android PopupWindow中EditText获取焦点自动弹出软键盘
公司的项目中要求在点击搜索的时候弹出一个搜索框,搜索框中有一个EditText,用于数据搜索关键字,要求在弹出PopupWindow的时候自动弹出软键盘,原以为只要写上着两行代码可以搞的问题: Inp ...
- HTML第二课——css
请关注公众号:自动化测试实战 先给大家提个建议,就是用sublime编辑器来编写.用其他的也无所谓,我只是建议,因为这个会帮你自动补全很多代码. css概念 css叫层叠样式表.意思就是一层一层的叠加 ...
- CentOS7防止CC攻击防止SSH爆破采取的措施
准备工作 1.检查firewalld是否启用 #如果您已经安装iptables建议先关闭 service iptables stop #查看firewalld状态 firewall-cmd --sta ...
- Ubuntu Linux下的Wireshark使用drcom_2011.lua分析drcom协议
用文本编辑器打开init.lua配置文件,位置一般是//usr/share/wireshark/init.lua: sudo vim /usr/share/wireshark/init.lua 在in ...