深入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和其他语言类似,在其语法中有两种赋值方式:引用赋值和非引用赋值(普通的==赋值)。

  <?php

  $a = 1;

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

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

  ?>

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

  


 

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

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

  <?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)的。

时间: 2025-01-21 13:09:50

深入PHP使用技巧之变量的相关文章

Android 优化二 Java内存分配机制及内存泄漏

Java内存分配机制及内存泄漏目录介绍 1.JVM内存管理 1.1 JVM内存管理图 1.2 Java采用GC进行内存管理. 2.JVM内存分配的几种策略 2.1 静态的 2.2 栈式的 2.3 堆式的 2.4 堆和栈的区别 2.5 得出结论 2.6 举个例子 2.7 调用 System.gc();进行内存回收 3.GC简单介绍 3.1 内存垃圾回收机制 3.2 关于GC介绍 3.3 如何监听GC过程 3.4 GC过程与对象的引用类型关系 4.内存泄漏简单介绍 4.1 内存泄漏的定义 4.2 内

DOS批处理高级教程 第七章 DOS批处理编程高级技巧_DOS/BAT

一.交互界面设计 没啥说的,看看高手设计的菜单界面吧: @echo off cls title 终极多功能修复 :menu cls color 0A echo. echo ============================== echo 请选择要进行的操作,然后按回车 echo ============================== echo. echo 1.网络修复及上网相关设置,修复IE,自定义屏蔽网站 echo. echo 2.病毒专杀工具,端口关闭工具,关闭自动播放 echo

CMD命令行高级教程精选合编合集_DOS/BAT

目录第一章 批处理基础第一节 常用批处理内部命令简介1.REM 和 ::2.ECHO 和 @3.PAUSE4.ERRORLEVEL5.TITLE6.COLOR7.mode 配置系统设备8.GOTO 和 :9.FIND10.START11.assoc 和 ftype12.pushd 和 popd13.CALL14.shift15.IF16.setlocal 与 变量延迟(ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION启动或停用延缓环境变量扩展名.)

bat命令入门与高级技巧详解_DOS/BAT

目录第一章 批处理基础第一节 常用批处理内部命令简介 1.REM 和 :: 2.ECHO 和 @ 3.PAUSE 4.ERRORLEVEL 5.TITLE 6.COLOR 7.mode 配置系统设备 8.GOTO 和 : 9.FIND 10.START 11.assoc 和 ftype 12.pushd 和 popd 13.CALL 14.shift 15.IF 16.setlocal 与 变量延迟(ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION

Shell编程基础篇-上

1.1 前言 1.1.1 为什么学Shell Shell脚本语言是实现Linux/UNIX系统管理及自动化运维所必备的重要工具, Linux/UNIX系统的底层及基础应用软件的核心大都涉及Shell脚本的内容.每一个合格 的Linux系统管理员或运维工程师,都需要能够熟练地编写Shell脚本语言,并能够阅 读系统及各类软件附带的Shell脚本内容.只有这样才能提升运维人员的工作效率,适 应曰益复杂的工作环境,减少不必要的重复工作,从而为个人的职场发展奠定较好的基础 1.1.2 什么是shell

变量命名的实用小技巧

变量|技巧 通常在写ActoinScript的时候,总盼望来些角本提示什么的.免得去想些常用的单词.其实有很多技巧的.举个例子:假设你想输入一个MovieClip实例名的时候,能在之后得到常用属性的代码提示如: ._currentframe ._alpha ._x .unloadMovie() 等一般的方法,你可以在此之前申明这个实例名为影片剪辑,如下: var movie:MovieClip //申明movie爲影片剪辑之后当你之后输入:  movie.   //实例名 + "." 

C语言中使用环境变量的技巧

实例1: 程序readenv.c显示了在c语言中存取环境变量的若干技巧.该程序将以与dos的set命令相同的格式打印出当前所有的环境变量,然后寻找path变量,打印出整个路径字符串.该程序中有两个重要的子程序,find_env_string(),它的主要作用是:根据从调用这传来的要求,或者从环境空间的起点开始,或者从上一次搜索後停留的地方开始,寻找随后的第一个环境字符串,具体程序清单如下. /**************readnv.c****************/ #include #in

PHP入门教程之自定义函数用法详解(创建,调用,变量,参数,返回值等)_php技巧

本文实例讲述了PHP自定义函数用法.分享给大家供大家参考,具体如下: Demo1.php <?php //标准函数,内置函数 echo md5('123456'); echo '<br/>'; echo sha1('123456'); echo '阅谁问君诵,水落清香浮.'; ?> Demo2.php <?php //创建函数,不要跟系统的内置函数重名 //函数有个特性,必须调用,才可以执行 //无参数表示()里面是空的,无返回就是函数的程序里没有 return functi

跟老男孩学Linux运维:Shell编程实战3.4 变量定义技巧总结

3.4 变量定义技巧总结 可以多学习和模仿操作系统自带的/etc/init.d/functions函数库脚本的定义思路,多学习Linux系统脚本中的定义,有经验的读者最终应形成一套适合自己的规范和习惯. (1)变量名及变量内容定义小结 变量名只能为字母.数字或下划线,只能以字母或下划线开头. 变量名的定义要有一定的规范,并且要见名知意. 示例: OldboyAge=1 #<==每个单词的首字母大写的写法 oldboy_age=1 #<==单词之间用"_"的写法 oldboy