搜索
首页php教程php手册深入PHP使用技巧之变量

  众所周知,PHP与其他脚本语言一样,属于弱变量类型的语言。同时PHP本身也是通过C语言来实现。本文主要介绍PHP内部是如何实现弱变量类型的,并且据此分析在PHP开发中需要注意的一些使用技术。其中会重点分析PHP中的copy on write机制和引用相关方面的话题。 本章节属于《深入PHP使用技巧》的第一部分。

  如何实现弱变量

  在了解PHP实现弱变量类型之前,可以先思考下:如何通过C/C++来实现弱变量类型的效果呢?

  这个问题我在BIT培训课上基本上有两种答案:

  方法1:采用C++的继承机制。首先定义一个基础类型

  Class Var

  {

  }

  然后基于Var,派生出不同的子类型IntVar/FloatVar/StringVar等等。

  方法2:基于C语言的 Struct。其中一个字段用于标识类型,另外一个字段用于存储数据,由于数据要是各种类型,所以通常需要采用指针

  比如:

  struct var {

  Int type;

  Void *data;

  };

  两种思路本身并没有太大区别,也都基本上能够满足需求。在PHP中采用了第二种思路,并且做了比较多的优化。在PHP中,所有的变量都会对应同一种类型zval,其中zval也就是struct _zval_struct,具体定义如下:

  typedef union _zvalue_value {

  long lval; /* long value */

  double dval; /* double value */

  struct {

  char *val;

  int len;

  } str;

  HashTable *ht; /* hash table value */

  zend_object_value obj;

  } zvalue_value;

  struct _zval_struct {

  /* Variable information */

  zvalue_value value; /* value */

  zend_uint refcount;

  zend_uchar type; /* active type */

  zend_uchar is_ref;

  };

  从zval可以看出,PHP在细节方面的确做了不少优化的功夫。

  1.zend_uchar type。采用uchar节省内存。

  2.zvalue_value value; 采用union来替换void *,这样同样能节省空间,并且比void *更能表义清晰。

  3.在字符串类型中,默认保留了字符串的长度。这样很容易做到字符串的二进制安全,并且在计算字符串长度的时候不需要进行扫描。

  观察PHP弱变量的实现,也会有以下疑惑:

  1.为什么会没有int类型呢?其实在PHP中是有的,只是说默认int数据就保存在long中。

  2.资源类型咋表现的呢?资源在PHP内部其实就是一数字。详细后续会介绍。

  3.refcount和is_ref是干嘛的呢?呵呵,这就是第二部分要介绍的了。

  Reference counting & Copy-on-Write

  PHP和其他语言类似,在其语法中有两种赋值方式:引用赋值和非引用赋值(普通的==赋值)。

  

  $a = 1;

  $b = $a;//非引用赋值

  $c = &$a;//引用赋值

  ?>

  引用赋值和非引用赋值在PHP内部是如何实现的呢?一种通常的认识是:“引用赋值就是两个变量对应同一个Zval,非引用赋值则是直接产生一个新的zval,同时把对应的值直接copy过来。”也就是该代码的内存结构如下:

  

 

  (该图是大多数人认为的PHP内存结构,是错误的)

  这样的确能够满足大部分情况下的需求,但显然不是最佳的解决方案,尤其是在内存管理上,比如说以下代码就会显得非常的低效。

  

  $arr = array(...);//定义一个非常大的PHP数组

  myfunc($arr);//每一个函数调用都是一次隐性的非引用赋值

  myfunc($arr);

  ?>

  因为每次函数调用会进行一次内存dump,而大内存的内存dump是非常耗CPU的。在C语言中,一种解决方案是采用指针,所有函数调用尽量传递指针。的确很灵活高效,但也很难维护~指针可以说是C语言程序员心头的痛(当然也是福~^_^)。还有一种更高级更有效的方法是采用引用计数(Reference counting)。

  在PHP中,也可以采用引用来解决这样的问题,但你见过采用在PHP中大量使用引用的吗?显然很少。

  在PHP内核中,Zval的实现正是采用了引用计数的概念,说起引用计数就不得不谈到copy-on-write 机制。这样前面谈到的refcount和is_ref就有作用了。

  refcount:引用次数。在zval初始创建的时候就为1。每增加一个引用,则refcount ++。

  is_ref:用于表示一个zval是否是引用状态。zval初始化的情况下会是0,表示不是引用。

  在Zend/Zend.h内部有一些关于ZVAL的宏定义,里面比较清晰的解析了引用计数的一些规则,其中重点关注以下几个宏定义

  #define INIT_PZVAL(z) \

  (z)->refcount = 1; \

  (z)->is_ref = 0;

  #define SEPARATE_ZVAL_IF_NOT_REF(ppzv) \//非引用下的变量分离

  if (!PZVAL_IS_REF(*ppzv)) { \

  SEPARATE_ZVAL(ppzv); \

  }

  #define SEPARATE_ZVAL_TO_MAKE_IS_REF(ppzv) \//非引用下的变量分离,并且设置引用

  if (!PZVAL_IS_REF(*ppzv)) { \

  SEPARATE_ZVAL(ppzv); \

  (*(ppzv))->is_ref = 1; \

  }

  #define SEPARATE_ARG_IF_REF(varptr) \ //引用下的变量分离

  if (PZVAL_IS_REF(varptr)) { \

  zval *original_var = varptr; \

  ALLOC_ZVAL(varptr); \

  varptr->value = original_var->value; \

  varptr->type = original_var->type; \

  varptr->is_ref = 0; \

  varptr->refcount = 1; \

  zval_copy_ctor(varptr); \

  } else { \

  varptr->refcount++; \

  }

  这里面谈到两个重要的概念:

  1、非引用下的变量分离。

  非引用下的变量分离,是指在一堆非引用变量中插入引用的情况下,在PHP内部进行的一种内存操作。以下面的列子来看:

  $a = 1;

  $b = $a;

  $c = &$b;

  在前两句执行之后,内存结构如下图

  

 

  在第三句 $c = &$b;语句中则会执行“非引用下的变量分离。”,具体步骤是:

  将b分离出来,同时把a对应的zval的refcount-1。

  copy 出一个新的zval,并把zval的is_ref设置成1.

  把C指向这个新的zval,同时refcount ++

  最终效果如下图:

  

 

  2、引用下的变量分离。

  引用下的变量分离,是指在一堆引用变量中进行一个非引用赋值操作,这个时候会直接执行copy内存的操作。

  以下面的例子来说

  $a = 1;

  $b = &$a;

  $c = $b;

  在执行完前两行后,PHP中内存结构如下:

  

 

  在第三句,则会执行“引用下的变量分离”也就是真正的copy,最终内存结构如下图

  

 

  据此,基本上对PHP变量内部的一些原理比较清楚了,但还有一些需要注意点的:

  1、PHP变量的引用计数特性,对于数组同样也存在。但注意,对于key则不生效。(具体在后面章节会分析到。)

  2、PHP变量中的对象比较特殊,在PHP5之后,默认都是采用引用赋值的方式。具体实现可以参考Zend_objects.*系列代码。

  3、对于分析PHP内部变量,推荐采用xdebug_debug_zval,而不要采用内置的debug_zval_dump。因为PHP内置的debug_zval_dump函数一方面无法处理is_ref,而且采用了引用的方式来处理,从而导致看到结果会有误解。

  使用技巧结论

  据此可以得出分析出不少结论:

  1、在PHP开发中不推荐采用引用。因为PHP内部对内存优化本身做了不少工作,引用不会带来太多优化。(但注意推荐非强制)

  2、在PHP中strlen是o(1)的。



声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
php怎么把负数转为正整数php怎么把负数转为正整数Apr 19, 2022 pm 08:59 PM

php把负数转为正整数的方法:1、使用abs()函数将负数转为正数,使用intval()函数对正数取整,转为正整数,语法“intval(abs($number))”;2、利用“~”位运算符将负数取反加一,语法“~$number + 1”。

php怎么实现几秒后执行一个函数php怎么实现几秒后执行一个函数Apr 24, 2022 pm 01:12 PM

实现方法:1、使用“sleep(延迟秒数)”语句,可延迟执行函数若干秒;2、使用“time_nanosleep(延迟秒数,延迟纳秒数)”语句,可延迟执行函数若干秒和纳秒;3、使用“time_sleep_until(time()+7)”语句。

php字符串有没有下标php字符串有没有下标Apr 24, 2022 am 11:49 AM

php字符串有下标。在PHP中,下标不仅可以应用于数组和对象,还可应用于字符串,利用字符串的下标和中括号“[]”可以访问指定索引位置的字符,并对该字符进行读写,语法“字符串名[下标值]”;字符串的下标值(索引值)只能是整数类型,起始值为0。

php怎么除以100保留两位小数php怎么除以100保留两位小数Apr 22, 2022 pm 06:23 PM

php除以100保留两位小数的方法:1、利用“/”运算符进行除法运算,语法“数值 / 100”;2、使用“number_format(除法结果, 2)”或“sprintf("%.2f",除法结果)”语句进行四舍五入的处理值,并保留两位小数。

php怎么根据年月日判断是一年的第几天php怎么根据年月日判断是一年的第几天Apr 22, 2022 pm 05:02 PM

判断方法:1、使用“strtotime("年-月-日")”语句将给定的年月日转换为时间戳格式;2、用“date("z",时间戳)+1”语句计算指定时间戳是一年的第几天。date()返回的天数是从0开始计算的,因此真实天数需要在此基础上加1。

php怎么读取字符串后几个字符php怎么读取字符串后几个字符Apr 22, 2022 pm 08:31 PM

在php中,可以使用substr()函数来读取字符串后几个字符,只需要将该函数的第二个参数设置为负值,第三个参数省略即可;语法为“substr(字符串,-n)”,表示读取从字符串结尾处向前数第n个字符开始,直到字符串结尾的全部字符。

php怎么替换nbsp空格符php怎么替换nbsp空格符Apr 24, 2022 pm 02:55 PM

方法:1、用“str_replace(" ","其他字符",$str)”语句,可将nbsp符替换为其他字符;2、用“preg_replace("/(\s|\&nbsp\;||\xc2\xa0)/","其他字符",$str)”语句。

php怎么查找字符串是第几位php怎么查找字符串是第几位Apr 22, 2022 pm 06:48 PM

查找方法:1、用strpos(),语法“strpos("字符串值","查找子串")+1”;2、用stripos(),语法“strpos("字符串值","查找子串")+1”。因为字符串是从0开始计数的,因此两个函数获取的位置需要进行加1处理。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

Dreamweaver Mac版

Dreamweaver Mac版

视觉化网页开发工具

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

适用于 Eclipse 的 SAP NetWeaver 服务器适配器

将Eclipse与SAP NetWeaver应用服务器集成。

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能