作者归档:admin

PHP内核中用户函数、内部函数和中间代码的转换

昨天和一朋友在邮件中讨论这样一个问题:zend_internal_function,zend_function,zend_op_array这三种结构是可以相互转化的,这三者的转化是如何进行的呢? 以此文,总结。

在函数调用的执行代码中我们会看到这样一些强制转换:

EX(function_state).function = (zend_function *) op_array;

或者:

EG(active_op_array) = (zend_op_array *) EX(function_state).function;

这种不同结构间的强制转换是如何进行的呢?

首先我们来看zend_function的结构,在Zend/zend_compile.h文件中,其定义如下:

typedef union _zend_function {
    zend_uchar type;    /* MUST be the first element of this struct! */

    struct {
        zend_uchar type;  /* never used */
        char *function_name;
        zend_class_entry *scope;
        zend_uint fn_flags;
        union _zend_function *prototype;
        zend_uint num_args;
        zend_uint required_num_args;
        zend_arg_info *arg_info;
        zend_bool pass_rest_by_reference;
        unsigned char return_reference;
    } common;

    zend_op_array op_array;
    zend_internal_function internal_function;
} zend_function;

这是一个联合体,我们来温习一下联合体的一些特性。 联合体的所有成员变量共享内存中的一块内存,在某个时刻只能有一个成员使用这块内存, 并且当使用某一个成员时,其仅能按照它的类型和内存大小修改对应的内存空间。 我们来看看一个例子:

#include <stdio.h>
#include <stdlib.h>

int main() {
    typedef  union _utype
    {
        int i;
        char ch[2];
    } utype; 

    utype a;

    a.i = 10;
    a.ch[0] = '1';
    a.ch[1] = '1';

    printf("a.i= %d a.ch=%s",a.i, a.ch);
    getchar();

    return (EXIT_SUCCESS);
}

程序输出:a.i= 12593 a.ch=11 当修改ch的值时,它会依据自己的规则覆盖i字段对应的内存空间。 ’1′对应的ASCII码值是49,二进制为00110001,当ch字段的两个元素都为’1′时,此时内存中存储的二进制为 00110001 00110001 转成十进制,其值为12593。

回过头来看zend_function的结构,它也是一个联合体,第一个字段为type, 在common中第一个字段也为type,并且其后面注释为/* Never used*/,此处的type字段的作用就是为第一个字段的type留下内存空间。并且不让其它字段干扰了第一个字段。 我们再看zend_op_array的结构:

struct _zend_op_array {
    /* Common elements */
    zend_uchar type;
    char *function_name;
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */

    zend_bool done_pass_two;
    ....//  其它字段
}

这里的字段集和common的一样,于是在将zend_function转化成zend_op_array时并不会产生影响,这种转变是双向的。

再看zend_internal_function的结构:

typedef struct _zend_internal_function {
    /* Common elements */
    zend_uchar type;
    char * function_name;
    zend_class_entry *scope;
    zend_uint fn_flags;
    union _zend_function *prototype;
    zend_uint num_args;
    zend_uint required_num_args;
    zend_arg_info *arg_info;
    zend_bool pass_rest_by_reference;
    unsigned char return_reference;
    /* END of common elements */

    void (*handler)(INTERNAL_FUNCTION_PARAMETERS);
    struct _zend_module_entry *module;
} zend_internal_function;

同样存在公共元素,和common结构体一样,我们可以将zend_function结构强制转化成zend_internal_function结构,并且这种转变是双向的。

总的来说zend_internal_function,zend_function,zend_op_array这三种结构在一定程序上存在公共的元素, 于是这些元素以联合体的形式共享内存,并且在执行过程中对于一个函数,这三种结构对应的字段在值上都是一样的, 于是可以在一些结构间发生完美的强制类型转换。 可以转换的列表如下:

  • zend_function可以与zend_op_array互换
  • zend_function可以与zend_internal_function互换

但是一个zend_op_array结构转换成zend_function是不能再次转变成zend_internal_function结构的,反之亦然。

其实zend_function就是一个混合的数据结构,这种结构在一定程序上节省了内存空间。

TIPI第六章发布

人生在世,如身处荆棘之中!心不动,人不妄动,不动则不伤。如心动,则人妄动,伤其身,痛其骨,于是体会到世间诸般痛苦。

选择在这个宜嫁娶、宜开光、宜安床的日子里,我们将第六章发布了。 没有华丽的出场,只有深夜的辗转,我们在努力。 虽然过程中有一些纷纷扰扰,但是经历了风雨的彩虹会更加美丽。

这次发布更新的主要内容有:

  1. 新增加第六章内存管理
  2. 增加CHM格式的支持
  3. 部分内容调整

TIPI团队

这次发布时我们提供了CHM版本的下载

TIPI入口>>>

PHP的比较方式(loose and strict comparison)

PHP的官方文档PHP type comparison tables 列出PHP类型和比较运算符在松散和严格比较时的作用。当阅读这篇说明时, 其中有一个NOTE:HTML 表单并不传递整数、浮点数或者布尔值,它们只传递字符串。 道出了我们WEB开发过程中数据交互的本质。 一如XML,JSON等等都是字符串,只是依照不同的需求制定规则,让人们能够更好的理解。 再看HTTP协议,不管是request还是response,最终的载体都是字符串,只是依照不同的字段和规则实现所需要的缓存优化、信息传递等功能。 有点扯远了,回到今天的主题,PHP的比较方式。

PHP的一些语言结构以及函数在默认实现上都是以松散比较的方式实现,如switch结构、array_keys、in_array、array_search等函数。 如果我们要使用严格的比较,对于switch语言结构本身并没有提供相关的实现,我们可以通过先将变量转换成对应的类型后再使用switch, 对于array_keys等函数,在PHP5以后都提供了$strict参数,将其设置为TRUE即可。 PHP的松散和严格比较表现最明显的是==和===。 我们从这两个语法结构来查看PHP对于松散和严格比较的实现。

从词法分析开始,在Zend/zend_language_scanner.l文件中我们找到==和===对应的token,如下:

<ST_IN_SCRIPTING>"===" {
    return T_IS_IDENTICAL;
}

<ST_IN_SCRIPTING>"==" {
    return T_IS_EQUAL;
}

PHP在词法结构上将这两种方式进行彻底的区分,==对应T_IS_EQUAL标识,===对应T_IS_IDENTICAL标识, 如果我们在写程序的过程中同时使用了以上两种符号会出现什么呢?如下代码:

$num = 1;
$str = "1";

if ($num === $str == $num) {
    echo 'phppan';
}

这里就不给出答案了(^_^)。

我们接着看语法解析,在Zend/zend_language_parse.y文件中我们可以看到如下代码:

|   expr T_IS_IDENTICAL expr        { zend_do_binary_op(ZEND_IS_IDENTICAL, &$$, &$1, &$3 TSRMLS_CC); }
|   expr T_IS_EQUAL expr            { zend_do_binary_op(ZEND_IS_EQUAL, &$$, &$1, &$3 TSRMLS_CC); }

仅仅是通过上面的所给的代码,不去查看Zend/zend_complice.c中关于zend_do_binary_op函数的实现, 我们可以猜测出其最终会生成ZEND_IS_EQUAL和ZEND_IS_IDENTICAL中间代码。 最终所有的ZEND_IS_IDENTICAL中间代码对应的执行函数都会调用is_identical_function函数实现严格对比, 所有的ZEND_IS_EQUAL中间代码对应的执行函数都会调用compare_function函数实现松散对比。

compare_function函数的具体实现在Zend/zend_operators.c文件。 compare_function的比较过程是一个穷举遍历比较的过程,程序在实现过程中对于传入的两个变量做类型的识别, 以两个变量的类型对为key,如字符串与字符串比较,数组和数组比较,浮点型和整型的比较,构造的类型对如果顺序不同,其对应的处理也是不同的。 如下列表,为所有可直接识别的类型对:

  • 整型和整型(LONG and LONG)
  • 浮点型和整型(DOUBLE and LONG)
  • 整型和浮点型(LONG and DOUBLE)
  • 浮点型和浮点型(DOUBLE and DOUBLE)
  • 数组和数组(ARRAY and ARRAY)
  • NULL和NULL
  • NULL和BOOL
  • BOOL和NULL
  • 字符串和字符串(STRING and STRING)
  • NULL和字符串
  • 字符串和NULL
  • 对象和NULL
  • NULL和对象
  • 对象和对象(OBJECT and OBJECT)

除此之外,还有一些特殊的情况,如下代码:

$arr = array(1);
$num = 1;

if ($arr == $num) {
    echo 'yes';
}else{
    echo 'no';
}

这些代码最终会输出no,但是在compare_function返回的结果为1。 他不属于上面所说的任一种类型对,由于第一个操作数为数组,则其走的程序流程分支为:

else if (Z_TYPE_P(op1)==IS_ARRAY) {
    ZVAL_LONG(result, 1);
    return SUCCESS;
}

以上的代码将1赋值给result,但是在中间代码的执行函数中,执行完compare_function后会执行如下代码:

ZVAL_BOOL(result, (Z_LVAL_P(result) == 0));

因为比较的最终结果为真假,而在比较中除了0,其他的都是不等于。

如果是比较字符串和整数,则其最终都将字符串转化成数字(整数或浮点数)再进行比较,即最终比较的方法是可以识别的类型对之一。 如果是数组和数组的比较,由于存在数组与数组的对比,则其直接调用zend_compare_arrays完成比较。

与松散比较类似,严格比较也有一个对应的函数–is_identical_function。 同样,其实现也在Zend/zend_operator.c文件。 和loose comparison不同,strict comparison在严格意义上来说进行的不是一个纯粹的比较过程,它首先会判断两个变量所处的ZVAL容器的类型是否一样, 如果不一样直接返回0,即不相等。 如果两个ZVAL窗口的类型一样,则根据比较的第一个操作数的类型作区分,然后判断两个操作数的值是否一样, 这里之所以要分别对待每种类型,是因为在PHP中不同类型的值存储的位置不同,其对应的获取方法也不相同。

在看完了PHP关于松散比较和严格比较的源码,我们最后看一段代码,你觉得应该会输出什么?

$str = "0";
if (empty($str)) {
    echo 1;
}

$str = "00";
if (empty($str)) {
    echo 2;
}