首页 > 编程语言 >深入PHP使用技巧之变量

深入PHP使用技巧之变量

时间:2023-01-01 18:00:46浏览次数:61  
标签:技巧 内存 value zval 引用 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;语句中则会执行“非引用下的变量分离。”,具体步骤是:

  1. 将b分离出来,同时把a对应的zval的refcount-1。
  2. copy 出一个新的zval,并把zval的is_ref设置成1.
  3. 把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)的。



标签:技巧,内存,value,zval,引用,PHP,变量
From: https://blog.51cto.com/u_3457306/5983196

相关文章

  • JavaScript奇淫技巧:反调试
    JavaScript奇淫技巧:反调试本文,将分享几种JS代码反调试技巧,目标是:实现防止他人调试、动态分析自己的代码。检测调试,方法一:用console.log检测代码:varc=newRegExp("1");c.......
  • Grafana 变量
    Grafana变量数据源主机名ip地址数据源label_values(job)nodeprometheus主机名label_values({job=~"$job"},nodename)k091ip地址label_values({job=~"$job",nodename=~......
  • thinkphp5框架使用总结
    一.路径访问方式http://网址/index.php/模块/控制器/操作.html其中:1.模块就是application下的一个文件夹2.控制器就是这个文件夹下面controler里的一个php文件3.操作就是......
  • PHP_EOL
     换行符unix系列用 \nwindows系列用 \r\nmac用 \rPHP中可以用PHP_EOL来替代,以提高代码的源代码级可移植性​​php​​    echo PHP_EOL;    //wi......
  • 变量和常量
    变量可以变化的量变量作用域类变量static修饰;随着类的出现而一起出现,随着类的消亡而一起消亡。实例变量从属于对象;不初始化也能使用;不初始化时默......
  • OpenCV常见的优化方法和技巧总结
    OpenCV常见的优化方法和技巧总结目录​​OpenCV常见的优化方法和技巧总结​​​​ 一、OpenCV常见的优化方法总结​​​​1.1cv::imread()设置reduce模式:​​​​1.2查表......
  • buuctf-web-[极客大挑战 2019]PHP 1
    知识点:文件备份、反序列化打开网站后发现源码没有提示,页面提示“备份的好习惯”,用御剑扫后台,扫出www.zip,打开发现有几个php文件打开index.php发现关键代码<?phpin......
  • JavaScript(JS基础、变量)
    编程语言编程概念:让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程。编程语言:机器语言汇编语言高级语言:C、C++、Java、C#等一、JavaScript1......
  • 变量、常量及作用域
    变量变量:可以变化的量Java是一种强类型语言,每个变量都必须声明其类型。Java变量是程序中最基本的存储单元,去要素包括变量名,变量类型和作用域。注意事项:每个变量都有......
  • 【每日小技巧】
    博客置顶说明:现有系列【入门书籍】:比较出名基础入门书籍复刻,章节代码练习、项目复现【积水成渊】:内容为一些语言源码、标准库、底层模块等,更新较慢,需要细水长流,不可贪快......