`
dcj3sjt126com
  • 浏览: 1830528 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

深入PHP使用技巧之变量

    博客分类:
  • PHP
阅读更多

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

 

如何实现弱变量

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

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

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

1
2
3
Class Var
{
}

然后基于Var,派生出不同的子类型IntVar/FloatVar/StringVar等等。
方法2:基于C语言的 Struct。其中一个字段用于标识类型,另外一个字段用于存储数据,由于数据要是各种类型,所以通常需要采用指针

比如:

1
2
3
4
struct var {
    Int type;
    Void *data;
};

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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和其他语言类似,在其语法中有两种赋值方式:引用赋值和非引用赋值(普通的==赋值)。

1
2
3
4
5
<?php
    $a = 1;
    $b = $a;//非引用赋值
    $c = &$a;//引用赋值
?>

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

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

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

1
2
3
4
5
<?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的宏定义,里面比较清晰的解析了引用计数的一些规则,其中重点关注以下几个宏定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#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内部进行的一种内存操作。以下面的列子来看:

1
2
3
$a = 1;
$b = $a;
$c = &$b;

在前两句执行之后,内存结构如下图
在第三句 $c = &$b;语句中则会执行“非引用下的变量分离。”,具体步骤是:

  1. 将b分离出来,同时把a对应的zval的refcount-1。
  2. copy 出一个新的zval,并把zval的is_ref设置成1.
  3. 把C指向这个新的zval,同时refcount ++

最终效果如下图:

2、引用下的变量分离。

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

以下面的例子来说

1
2
3
$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)的。

本文首发xlq的博客(转载请保留)http://blog.xiuwz.com/2011/11/09/php-using-internal-zval/
分享到:
评论

相关推荐

    PHPWeb开发技术指南——pdf格式

    第三部分 深入研究PHP 第9章 扩充PHP 4.0:探究PHP内核 221 9.1 概述 221 9.2 什么是Zend?什么是PHP? 221 9.3 扩充可能性 222 9.3.1 外部模块 222 9.3.2 内嵌模块 223 9.3.3 Zend引擎 223 9.4 源代码格式 223 ...

    PHP和MySQL Web开发第4版pdf以及源码

    《php和mysql web开发(原书第4版)》:开发人员专业技术丛书。 目录 读者反馈 译者序 前言 作者简介 第一篇 使用PHP ...18.6 在PHP中使用加密技术 18.6.1 安装GPG 18.6.2 测试GPG 18.7 进一步学习 ...

    PHP4.0 高级篇

    第三部分 深入研究PHP 第9章 扩充PHP 4.0:探究PHP内核 221 9.1 概述 221 9.2 什么是Zend?什么是PHP? 221 9.3 扩充可能性 222 9.3.1 外部模块 222 9.3.2 内嵌模块 223 9.3.3 Zend引擎 223 9.4 源代码格式 223 ...

    PHP和MySQL WEB开发(第4版)

    18.6 在PHP中使用加密技术 18.6.1 安装GPG 18.6.2 测试GPG 18.7 进一步学习 18.8 下一章 第四篇 PHP的高级技术 第19章 与文件系统和服务器的交互 19.1 文件上载 19.1.1 文件上载的HTML代码 19.1.2 编写处理文件的...

    史上最全韩顺平传智播客PHP就业班视频,10月份全集

    9-17 3.php数据库编程(11)-使用mysqli扩展库增强(预处理技术) 9-17 4.预定义超全局数组①-原理分析 $_GET 9-17 5.预定义超全局数组②-$_POST $_REQUEST 9-17 6.预定义超全局数组③-$_SERVER $_ENV $GLOBALS 9-17 7....

    史上最全韩顺平传智播客PHP就业班视频,9月份全集

    9-17 3.php数据库编程(11)-使用mysqli扩展库增强(预处理技术) 9-17 4.预定义超全局数组①-原理分析 $_GET 9-17 5.预定义超全局数组②-$_POST $_REQUEST 9-17 6.预定义超全局数组③-$_SERVER $_ENV $GLOBALS 9-17 7....

    PHP语言教程&案例&相关项目

    PHP是一种流行的服务器端脚本语言...通过逐步深入的学习,您可以掌握PHP的基本编程技巧和用法。此外,还有一些教程会介绍PHP与数据库交互、表单处理、会话管理、安全性等方面的知识,这些都是Web开发中非常重要的内容。

    PHP和MySQL Web开发第4版

    《php和mysql web开发(原书第4版)》:开发人员专业技术丛书。 目录 读者反馈 译者序 前言 作者简介 第一篇 使用PHP ...18.6 在PHP中使用加密技术 18.6.1 安装GPG 18.6.2 测试GPG 18.7 进一步学习 ...

    (全)传智播客PHP就业班视频完整课程

    9-17 3.php数据库编程(11)-使用mysqli扩展库增强(预处理技术) 9-17 4.预定义超全局数组①-原理分析 $_GET 9-17 5.预定义超全局数组②-$_POST $_REQUEST 9-17 6.预定义超全局数组③-$_SERVER $_ENV $GLOBALS 9-17 7....

    PHP实现简单线性回归之数据研究工具

    在深入学习更高级的技术之前,对于简单线性回归的透彻理解将使您受益匪浅。即使简单线性回归只用一个变量来说明或预测另一个变量的偏离值,在所有的研究变量之间寻找简单线性关系仍然常常是研究性数据分析的第一步。...

    PHP的概要介绍与分析

    《PHP全栈开发实战课程》是一门全面、实战导向的在线培训课程,旨在从零基础到精通,引领学员深入理解PHP语言核心、Web开发基础、数据库操作、框架应用及现代Web服务开发技术。本课程由拥有多年PHP开发经验的行业...

    史上最全传智播客PHP就业班视频课,8月份视频

    9-17 3.php数据库编程(11)-使用mysqli扩展库增强(预处理技术) 9-17 4.预定义超全局数组①-原理分析 $_GET 9-17 5.预定义超全局数组②-$_POST $_REQUEST 9-17 6.预定义超全局数组③-$_SERVER $_ENV $GLOBALS 9-17 7....

    韩顺平PHP JS JQUERY 所有视频下载种子 货真价实

    9-17 2.php数据库编程(10)-使用mysqli扩展库增强(预处理技术) 9-17 3.php数据库编程(11)-使用mysqli扩展库增强(预处理技术) 9-17 4.预定义超全局数组①-原理分析 $_GET 9-17 5.预定义超全局数组②-$_POST $_REQUEST ...

    LINUX网站建设技术指南

    8.4.4 使用环境变量获取客户端信息 8.5 基本控制流程:分支和循环 8.5.1 条件语句和分支结构 8.5.2 循环语句 8.6 表达式基础 8.6.1 算术运算符 8.6.2 字符串运算符 8.6.3 赋值运算符 8.6.4 位运算符 8.6.5 逻辑...

    代码审计 企业级Web代码安全架构

    第4~6章则介绍常见漏洞的审计方法,分别对应基础篇、进阶篇以及深入篇,涵盖SQL注入漏洞、XSS漏洞、文件操作漏洞、代码/命令执行漏洞、变量覆盖漏洞以及逻辑处理等漏洞;第7章介绍二次漏洞的挖掘方法;第8章介绍...

    代码审计:企业级Web代码安全架构

    第4~6章则介绍常见漏洞的审计方法,分别对应基础篇、进阶篇以及深入篇,涵盖SQL注入漏洞、XSS漏洞、文件操作漏洞、代码/命令执行漏洞、变量覆盖漏洞以及逻辑处理等漏洞;第7章介绍二次漏洞的挖掘方法;第8章介绍...

    深入讲解PHP Session及如何保持其不过期的方法

    SESSION的实现中采用COOKIE技术,SESSION会在客户端保存一个包含session_id(SESSION编号)的COOKIE;在服务器端保存其他session变量,比如session_name等等。当用户请求服务器时也把session_id一起发送到服务器,通过...

    JSP高级编程

    本书既适合初学者阅读,也适合具有一定JSP基础的开发人员深入研究使用。 前 言 JSP是SUN公司推出的一种新型的Internet/Intranet开发语言,和前一代Internet/Intranet开发语言(ASP、PHP)相比,JSP在以下几个方面...

Global site tag (gtag.js) - Google Analytics