菜鸟学php扩展 之 详解php扩展的变量(四)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011957758/article/details/72633938

转载请附上本文地址:http://blog.csdn.net/u011957758/article/details/72633938

前言

经过前文 菜鸟学php扩展 之 详解扩展函数的传参(如何获取参数)(三) ,基本上已经明白了php调用扩展时候,按道理应该要是来记录一下,如何返回结果的。但是突然总结时候发现,结果的返回之前必须要记录一下php扩展的变量相关知识点,这样更好理解如何返回结果,毕竟返回的结果基本都是变量。

正文

变量的结构与类型

PHP在内核中是通过zval这个结构体来存储变量的,所以写扩展的时候当然也是一样。
在Zend/zend.h文件中找到了其定义:

struct _zval_struct {
    zvalue_value value; /* 变量的值 */
    zend_uint refcount__gc;
    zend_uchar type;    /* 变量当前的数据类型 */
    zend_uchar is_ref__gc;
};

快速理解:为方便起见可以直接把zval结构体理解成一个由value、type、refcount__gc、is_ref__gc组成的对象就好了,*__gc是管理内存相关的时候会用到的,这里可以先不管。

基于zval结构体,衍生出了8种数据类型。也就是php语言对应的8种类型,这些数据类型在内核中的分别对应于特定的常量:

常量 解释
IS_NULL 变量的默认类型,此类型值只有一个就是NULL,不同于0和false。
IS_BOOL 布尔类型的变量,有两个值:true和false。
IS_LONG PHP语言中的整型,内核中是通过所在操作系统的signed long数据类型来表示的。 32位操作系统中,可存储-2147483648 到 +2147483647范围内的任一整数。如果PHP语言中的整型变量超出范围,它并不会直接溢出,而是会被内核转换成IS_DOUBLE类型的值然后再参与计算。同时也解释了PHP语言中的整型数据都是带符号的缘由。
IS_DOUBLE PHP中的浮点数据,内核中是通过操作系统的signed double型变量来存储的。这个浮点数是有坑存在的,附上鸟哥亲自解释的坑。点这里
IS_STRING PHP中的字符串类型,PHP内核在zval结构里保存着这个字符串的值与实际长度。
IS_ARRAY 数组类型,它唯一的功能就是聚集别的变量。由于是基于HashTable实现的,所以可以存储任意类型的变量,每个HashTable中的元素都有两部分组成:索引与值,每个元素的值都是一个独立的zval(确切的说应该是指向某个zval的指针)。
IS_OBJECT 对象类型,和数组一样,也是用来存储复合数据的,但是与数组不同的是,对象还需要保存以下信息:方法、访问权限、类常量以及其它的处理逻辑。
IS_RESOURCE 资源类型,存储一些内容可能无法直接呈现给PHP用户的,但实际中却是需要的东西,比如与某台mysql服务器的链接,或者直接呈现出来也没有什么意义的东西。

如何检测变量的类型和值

检测变量的类型

这几个宏都可以表示:Z_TYPE、Z_TYPE_P、Z_TYPE_PP。
区别是:一个p结尾的宏的参数大多是*zval型变量(指针),两个p结尾的宏参数大多是**zval型变量(指针的指针).
简单粗暴的程序表达:

void display_value(zval zv,zval *zv_p,zval **zv_pp)
{
    if( Z_TYPE(zv) == IS_NULL ){ 
        //类型是 IS_NULL
    }

    if( Z_TYPE_P(zv_p) == IS_LONG ){
        //类型是 IS_LONG
    }

    if(Z_TYPE_PP(zv_pp) == IS_DOUBLE ){
        //类型是 IS_DOUBLE
    }
}   

检测变量的值

操作的宏都定义在/Zend/zend_operators.h文件里:

//操作整数的
#define Z_LVAL(zval)            (zval).value.lval
#define Z_LVAL_P(zval_p)        Z_LVAL(*zval_p)
#define Z_LVAL_PP(zval_pp)      Z_LVAL(**zval_pp)

//操作IS_BOOL布尔型的
#define Z_BVAL(zval)            ((zend_bool)(zval).value.lval)
#define Z_BVAL_P(zval_p)        Z_BVAL(*zval_p)
#define Z_BVAL_PP(zval_pp)      Z_BVAL(**zval_pp)

//操作浮点数的
#define Z_DVAL(zval)            (zval).value.dval
#define Z_DVAL_P(zval_p)        Z_DVAL(*zval_p)
#define Z_DVAL_PP(zval_pp)      Z_DVAL(**zval_pp)

//操作字符串的值和长度的
#define Z_STRVAL(zval)          (zval).value.str.val
#define Z_STRVAL_P(zval_p)      Z_STRVAL(*zval_p)
#define Z_STRVAL_PP(zval_pp)        Z_STRVAL(**zval_pp)

#define Z_STRLEN(zval)          (zval).value.str.len
#define Z_STRLEN_P(zval_p)      Z_STRLEN(*zval_p)
#define Z_STRLEN_PP(zval_pp)        Z_STRLEN(**zval_pp)

#define Z_ARRVAL(zval)          (zval).value.ht
#define Z_ARRVAL_P(zval_p)      Z_ARRVAL(*zval_p)
#define Z_ARRVAL_PP(zval_pp)        Z_ARRVAL(**zval_pp)

//操作对象的
#define Z_OBJVAL(zval)          (zval).value.obj
#define Z_OBJVAL_P(zval_p)      Z_OBJVAL(*zval_p)
#define Z_OBJVAL_PP(zval_pp)        Z_OBJVAL(**zval_pp)

#define Z_OBJ_HANDLE(zval)      Z_OBJVAL(zval).handle
#define Z_OBJ_HANDLE_P(zval_p)      Z_OBJ_HANDLE(*zval_p)
#define Z_OBJ_HANDLE_PP(zval_p)     Z_OBJ_HANDLE(**zval_p)

#define Z_OBJ_HT(zval)          Z_OBJVAL(zval).handlers
#define Z_OBJ_HT_P(zval_p)      Z_OBJ_HT(*zval_p)
#define Z_OBJ_HT_PP(zval_p)     Z_OBJ_HT(**zval_p)

#define Z_OBJCE(zval)           zend_get_class_entry(&(zval) TSRMLS_CC)
#define Z_OBJCE_P(zval_p)       Z_OBJCE(*zval_p)
#define Z_OBJCE_PP(zval_pp)     Z_OBJCE(**zval_pp)

#define Z_OBJPROP(zval)         Z_OBJ_HT((zval))->get_properties(&(zval) TSRMLS_CC)
#define Z_OBJPROP_P(zval_p)     Z_OBJPROP(*zval_p)
#define Z_OBJPROP_PP(zval_pp)       Z_OBJPROP(**zval_pp)

#define Z_OBJ_HANDLER(zval, hf)     Z_OBJ_HT((zval))->hf
#define Z_OBJ_HANDLER_P(zval_p, h)  Z_OBJ_HANDLER(*zval_p, h)
#define Z_OBJ_HANDLER_PP(zval_p, h)     Z_OBJ_HANDLER(**zval_p, h)

#define Z_OBJDEBUG(zval,is_tmp)     (Z_OBJ_HANDLER((zval),get_debug_info)?  \
                        Z_OBJ_HANDLER((zval),get_debug_info)(&(zval),&is_tmp TSRMLS_CC): \
                        (is_tmp=0,Z_OBJ_HANDLER((zval),get_properties)?Z_OBJPROP(zval):NULL)) 
#define Z_OBJDEBUG_P(zval_p,is_tmp) Z_OBJDEBUG(*zval_p,is_tmp) 
#define Z_OBJDEBUG_PP(zval_pp,is_tmp)   Z_OBJDEBUG(**zval_pp,is_tmp)

//操作资源的
#define Z_RESVAL(zval)          (zval).value.lval
#define Z_RESVAL_P(zval_p)      Z_RESVAL(*zval_p)
#define Z_RESVAL_PP(zval_pp)        Z_RESVAL(**zval_pp)

改写上文的例子:

void display_value(zval zv,zval *zv_p,zval **zv_pp)
{
    if( Z_TYPE(zv) == IS_NULL ) {
        php_printf("类型是 IS_NULL!\n");
    }

    if( Z_TYPE_P(zv_p) == IS_LONG ) {
        php_printf("类型是 IS_LONG,值是:%ld" , Z_LVAL_P(zv_p));
    }

    if(Z_TYPE_PP(zv_pp) == IS_DOUBLE ) {
        php_printf("类型是 IS_DOUBLE,值是:%f" , Z_DVAL_PP(zv_pp) );
    }
}   

如何存储变量

当PHP中定义了一个变量,内核会自动的把它的信息储存到一个用HashTable实现的符号表里。

全局作用域的符号表是在调用扩展的RINIT方法(一般都是MINIT方法里)前创建的,并在RSHUTDOWN方法执行后自动销毁。(看不懂请点击 菜鸟学php扩展 之 自动生成的扩展框架详解(二) )

此时,当在PHP中调用一个函数或者类的方法时,内核会创建一个新的符号表并激活,一个代表全局作用域的,另一个代表当前作用域(现在懂了内核是怎么区分局部变量的了吧,这也是为什么我们无法在函数中使用在函数外定义的变量的原因) ,如果不是在一个函数里,则全局作用域的符号表处于激活状态。

打开源码看看$GLOBAL的定义(Zend/zend_globals.h):

struct _zend_executor_globals {
    ...
    HashTable symbol_table; //
    HashTable *active_symbol_table;
    ...
}; 

可以通过EG宏来进行访问,例如:EG(symbol_table)、EG(active_symbol_table)。

而PHP语言中使用的$GLOBALS,其实从根本上来讲,是EG(symbol_table)的一层封装而已。

会发现一个是真正的HashTable,另一个是指针。是的。当对HashTable进行操作的时候,往往是将它的地址传给函数,所以需要读取其中的值。

先在通过一个的例子来完全理解上述描述:
在php中

<?php
    $cbs = '咖啡色的羊驼';
?>

在内核中

{
    zval *cbsval; //声明一个zval的指针

    MAKE_STD_ZVAL(cbsval); //申请一块内存
    ZVAL_STRING(cbsval, "咖啡色的羊驼", 1); //通过ZVAL_STRING宏将值设置为"咖啡色的羊驼" (这个宏会在下文详解)
    ZEND_SET_SYMBOL( EG(active_symbol_table) ,  "cbs" , cbsval); //将这个zval加入到当前的符号表里去,并将其label定义成cbs
}  

如何给变量赋值

知道如何存储之后,知道有一步关键步骤是给变量赋值。不同类型的变量,都提供了不同的宏来实现其赋值:

类型 实际实现
IS_NULL ZVAL_NULL(pvz) Z_TYPE_P(pzv)=IS_NULL;
IS_BOOL ZVAL_BOOL(pzv, b);
ZVAL_TRUE(pzv);
ZVAL_FALSE(pzv);
Z_TYPE_P(pzv)=IS_BOOL;
Z_BVAL_P(pzv)=b ? 1 : 0;
另两个:
ZVAL_BOOL(pzv, 1);
ZVAL_BOOL(pzv, 0);
IS_LONG ZVAL_LONG(pzv, l); Z_TYPE_P(pzv)=IS_LONG;
Z_LVAL_P(pzv)=l;
IS_DOUBLE ZVAL_DOUBLE(pzv, d); Z_TYPE_P(pzv)=IS_DOUBLE;
Z_DVAL_P(pzv)=d;
IS_STRING ZVAL_STRINGL(pzv,str,len,dup);
ZVAL_STRING(pzv, str, dup);
Z_TYPE_P(pzv)=IS_STRING;
Z_STRLEN_P(pzv) = len;
if (dup)
{Z_STRVAL_P(pzv) =estrndup(str, len + 1);}
else
{Z_STRVAL_P(pzv) = str;}

另一个是:

ZVAL_STRINGL(pzv, str,strlen(str), dup);
IS_RESOURCE ZVAL_RESOURCE(pzv, res); Z_TYPE_P(pzv)=IS_RESOURCE;
Z_RESVAL_P(pzv)=res;



疑问点:

1.ZVAL_STRINGL(pzv,str,len,dup)中的dup参数

dup指明该字符串是否需要被复制。值为 1 将先申请一块新内存并赋值该字符串,然后把新内存的地址复制给pzv,为 0 时则是直接把str的地址赋值给zval。

2.ZVAL_STRINGL与ZVAL_STRING的区别

ZVAL_STRINGL是显式指定了长度后进行赋值,ZVAL_STRING由内部进行长度计算并赋值。ZVAL_STRINGL效率高于ZVAL_STRING,因为不需要计算长度。

如何查找变量

zend_hash_find()函数是内核提供的操作HashTable的API之一,来找到当前某个作用域下已经定义好的变量。
上例子:

{
    zval **cbsval;

    if (zend_hash_find(
            EG(active_symbol_table), //这个参数是地址,如果我们操作全局作用域,则需要&EG(symbol_table)
            "cbs",
            sizeof("cbs"),
            (void**)&cbsval
        ) == SUCCESS
    ){
        // 找到$cbs啦
    }else{
        // 没找到$cbs
    }
}       

如何强制转换变量类型

php内核专门提供了convert_to_*()系列的函数供强制类型转换。
php中:

$cbs = 1;
$cbs = (string)$cbs;//强制类型转换

内核中:

//将任意类型的zval转换成字符串
void change_zval_to_string(zval *value) {
    convert_to_string(value);
}

除了convert_to_string外还有:

类型 函数
IS_NULL convert_to_null(zval *op)
IS_BOOL convert_to_boolean(zval *op);
IS_LONG convert_to_long(zval *op);
IS_DOUBLE convert_to_double(zval *op);
IS_STRING convert_to_string(zval *op);
IS_ARRAY convert_to_array(zval *op);
IS_OBJECT convert_to_object(zval *op);
IS_RESOURCE 资源的值在用户层面上,根本就没有意义,内核不会对它的值进行转换。

如何管理和回收变量占用的内存

假设存在以下代码:

$a = 1;
$b = $a;

我们知道每次赋值,系统都会申请一块内存供变量存放,假设设置$a变量申请了50b内存,设置 $b 变量的时候又申请了50b内存。

那么这里有个坑,万一程序里头操作的变量很多(文件内容等)而且又是相同的那不是要申请很多内存?

答案是 no,php会进行合并同类项。还记得定义变量时候的zval结构体中有两个参数refcount__gc和is_ref__gc吗?上文没讲解。
是的,就是用它来进行合并同类项(内存管理)。

真实过程是这样:

//按正常的变量设置申请内存,并赋值。初始化zval机构体中refcount__gc成员的值会被初始化为1$a = 1; 

//发现$b$a是一样,将refcount__gc属性加1变成2,因为现在有两个变量在用这个zval结构了;不再重复申请内存。
$b = $a;

//unset的时候,讲refcount__gc属性减1就好。
unset($a);

那如果来个 $b+=5呢?

//同上
$a = 1; 

//同上
$b = $a;

//内核首先查看refcount__gc属性,如果它大于1则为这个变化的变量从原zval结构中复制出一份新的专属与$b的zval来,并改变其值。同时将原zval结构体中的refcount__gc减一
$b+=100;

如果存在引用呢?$b=&$a?

//同上
$a = 1;

//refcount__gc属性向往常一样增长为2,而且is_ref__gc属性也同时变为了1,代表是引用,如果有值的变化无需再次复制一个zval结构体
$b = &$a;

//同上&$a也变成101了
$b += 100;

注意:是否需要复制一个zval的条件是:refcount__gc==2 && is_ref__gc == 0

那如果加入一个第三者:

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

太复杂,配上图的解释:
这里写图片描述

那如果地址的赋值在第二步?

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

配上图的解释:
这里写图片描述

结束语

终于可以在下一篇笔记愉快的写函数的返回值了。

没有更多推荐了,返回首页