KMP字符串比较算法(php版本)

2016年7月5日 没有评论

前段时间面试的时候,被一个公司的人问到过,今天正好在地铁上面看到了关于KMP算法的文章,作者写的很好,我这个算法渣都看懂了!于是就抽空写了这个PHP版本的kmp算法实现,写的不好的地方欢迎指正。

<?php
/**
 * kmp字符串比较算法
 */

/**
 * 获得所有前缀集合
 * @param string $string
 *
 * @return array
 */
function get_prefix_set($string){
    $len = strlen($string);
    if($len <= 1 ) return [];
    $prefixs = [];
    for($i=1;$i<$len;$i++){
        $prefixs[] = substr($string,0,$i);
    }
    return $prefixs;
}

/**
 * 获得所有后缀集合
 * @param string $string
 *
 * @return array
 */
function get_suffix_set($string){
    $len = strlen($string);
    if($len<=1) return [];
    $suffixs = [];
    for($i=1;$i<$len;$i++){
        $suffixs[] = substr($string,$i);
    }
    return $suffixs;
}

/**
 * 查找字符串$str2是否在$str1中出现
 * @param string $str1
 * @param string $str2
 *
 * @return bool
 */
function my_strstr($str1,$str2){
    $prefixSet = get_prefix_set($str2);
    $suffixSet = get_suffix_set($str2);
    $intersection = array_intersect($prefixSet,$suffixSet);
    $matchValue = 0;
    if(!empty($intersection)){
        foreach($intersection as $tmpstr){
            $len = strlen($tmpstr);
            if($len>$matchValue) $matchValue = $len;
        }
    }
    $str1_len = strlen($str1);
    $str2_len = strlen($str2);
    $endPos = $str1_len-$str2_len;
    if($endPos<0){
        return false;
    }
    for($i=0;$i<=$endPos;){
        $moveStep = 1;
        for($n=0;$n<$str2_len;$n++){
            if($str1[$i+$n] != $str2[$n]){//字符串不匹配时,跳出本次循环
                $moveStep = $n-$matchValue;
                $moveStep = $moveStep > 0 ? $moveStep : 1;
                break 1;
            }else if($str1[$i+$n] == $str2[$n] && $n==$str2_len-1){
                return true;
            }
        }
        $i +=$moveStep;
    }
    return false;
}

$result = my_strstr("abcabfcde","bfcd");
var_dump($result);

如果对KMP算法不是特别了解可以看一下《字符串匹配的KMP算法》

mysql重置root密码

2016年4月14日 没有评论

今天在mac上面安装mysql,安装之后用root登录,发现进不去,说需要密码,使用DMG包安装时提示我生成的随机密码也尝试了,依旧不行!不过生成root密码页面提示如果无法登录可以尝试查看How to Reset the Root Password,进行重置root密码工作。如果你觉得自己英文还行可以直接参考前面那个连接去查看,好了,下面进入正题重置密码。

我是在mac下面做的重置root操作,不过windows下面的很类似,本文的必要条件是你有管理mysql服务器账号或者在本机上面,如果上面2个都不满足,那么这篇文章就不适合你了。

第一步关闭正在运行mysql服务器。

官网手册的命令是这样的,不过你可以像我这样通过ps查看mysql的进程id,然后kill它,当然这里最好把ps中启动mysql的命令先记录下来,后面会使用的。

kill `cat /mysql-data-directory/host_name.pid`
#ps方法
ps aux|grep 'mysql'
#这里把%pid%替换为上面ps给的进程id
sudo kill %pid%

第二步建立mysql init文件
这一步是要建立一个mysql init文件,配合mysqld的–init-file参数来使用的.init-file参数英文说明是”Read SQL commands from this file at startup”,我的个人理解是读取指定文件中的sql语句,并在mysql启动的时候执行它们。
mysql版本5.7.6和以后使用”ALTER USER ‘root’@’localhost’ IDENTIFIED BY ‘MyNewPass';”,如果你的版本是其他版本可以使用”SET PASSWORD FOR ‘root’@’localhost’ = PASSWORD(‘MyNewPass’);”尝试一下。

#5.7.6和以后版本
ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass';
#5.7.6以前版本
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('MyNewPass');

保存文件。
第三步重启mysql
这时候我们来重启我们的mysql,不过是加上–init-file参数的命令。还记得我上面让你记住的ps下面的mysql启动命令吧,就是这里用的。官方手册是”mysqld_safe –init-file=/home/me/mysql-init &”,也许在其他类*unix系统上面可以,但是我在mac下面尝试了,启动不起来!如果你是centos或者ubuntu用户可以尝试一下官方手册的命令,不过mac下面还是建议使用ps中显示的命令,再后面追加–init-file参数。


#官网手册命令,mac用户不推荐

mysqld_safe --init-file=/home/me/mysql-init &

#mac用户推荐命令

sudo /usr/local/mysql/bin/mysqld --user=_mysql --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --log-error=/usr/local/mysql/data/mysqld.local.err --pid-file=/usr/local/mysql/data/mysqld.local.pid --init-file=/Users/king/sql/alter_root.sql &

“/Users/king/sql/alter_root.sql”为你第二步是保存的sql文件.此时再使用root用户用你重置的命令登录就可以成功登录了!

windows下面的重置root密码功能应该和mac下面类似,只要把shell命令换成dos命令即可,具体的可以参考上面的官网手册地址。

centos安装EPEL源

2016年4月7日 没有评论

在虚拟机里面使用我的one_shell_install_lnmp脚本安装lnmp环境的时候,发现报错了,提示缺少libmcrypt-devel包。以前我是在centos7下面测试过我的脚本的,是可以通过的,于是我就又检查了一下脚本,里面有安装libmcrypt-devel 这个选项。我又试着单独执行了一下安装libmcrypt-devel包的命令,依然是没有任何反应。刚开始我第一时间没有想到源的问题,类似以往的习惯去google,发现也有同学和我类似,说163源上面没有libmcrypt-devel这个包,于是就有了今天这个文章。当然这篇文章是笔记性质的,如果对你有帮助,十分荣幸,有问题也可以留言。

EPEL是什么

EPEL全名是Extra Packages for Enterprise Linux(epel wiki:https://fedoraproject.org/wiki/EPEL),是一个由特别兴趣小组创建、维护并管理的,针对 红帽企业版 Linux(RHEL)及其衍生发行版(比如 CentOS、Scientific Linux、Oracle Enterprise Linux)的一个高质量附加软件包项目。

其实我也是今天才了解的EPEL,以前虽然使用过它,但是对它没有什么系统了解,上面介绍它的内容也是摘自wiki。

EPEL安装

我写了一个install_epel.sh的shell脚本, 功能就是检测当前系统是否有epel源,如果不存在就安装。


#!/bin/bash

#检查epel源是否安装
yum repolist|egrep "epel" >& /dev/null
if [ $? -ne 0 ];then
epel_file="epel.noarch.rpm"
wget http://dl.fedoraproject.org/pub/epel/7/x86_64/e/epel-release-7-5.noarch.rpm -O $epel_file
rpm -ivh $epel_file
rm -f $epel_file
yum makecache
fi

关于yum或者epel其他介绍,请自行google!

分类: Linux 标签: ,

Systemd配置文件service小解

2015年11月30日 2 条评论

最早看到systemd还是在微博上面看到陈浩在回答别人如何解决死掉的进程如何重启的,陈浩说systemd这个“神器”可以轻松解决这个问题。在那之前我对systemd还是一无所知的,所以特意google了一下systemd到底是个神马东西。

网上对systemd的评价毁誉参半,抵制systemd说它不稳定有时候可能crash,或者说脚本从原始Sysvinit移植到systemd会花费运维人员大量的时间(包含systemd的学习时间)之类的,甚至有人拿出来Unix的哲学“ do one thing and do it well”,说systemd做了系统启动等等很多工作,不见得都能把它做好。对于这些种种理由,我觉得没必要一一反击,看看这一篇《浅析 Linux 初始化 init 系统,第 3 部分: Systemd》。当然我觉得对开发和运维人员非常有必要学习systemd的原因是redhat7系列(centos7),debian系列等等linux主要衍生版本在他们的新版本中都在转投systemd怀抱,所以如果你是linux系统的使用者,我觉得学习了解systemd还是非常非常有必要的。

service配置样例

这是一个简单的脚本启动,进程终止以后会自动重启!

[Unit]
Description=a php im push script
Wants=network.target

[Service]
Type=simple
ExecStart=/path/to/your/script
Restart=always

[Install]
WantedBy=multi-user.target

Description是对服务的描述,方便记忆和阅读,一般来说这里可以随心所欲的。
Wants是服务项的启动依赖那些项。
Type是服务的类型,有simple,forking,oneshot,dbus,notify,idle。其中我们常用的类型有simple,forking,oneshot。simple 是默认,这是最简单的服务类型。意思就是说启动的程序就是主体程序,这个程序要是退出那么一切都退出。forking 标准 Unix Daemon 使用的启动方式。启动程序后会调用 fork() 函数,把必要的通信频道都设置好之后父进程退出,留下守护精灵的子进程。oneshot种服务类型就是启动,完成,没进程了。
ExecStart是启动脚本的路径和参数,参数和脚本之间用空格分割。
Restart是配置启动项是否需要重启,有no,on-success,on-failure,on-abnormal,on-watchdog,on-abort,always。这些选项就是他们字面意思,abnormal意思是反常的,看文档里面说除了信号SIGHUP,SIGINT,SIGTERM,SIGPIPE以外其他信号终止的,都是反常的包含code dump,操作超时等。
WantBy指服务在何种情况下面会被启动,例如上面multi-user.target指的是多用户环境。
当然我觉得这几个简单的配置项几乎就可以解决我们日常60%的问题了,其他特殊的需求可以查看文档。
如果你的英文还不错,可以查看下面2个英文手册,service手册unit手册

分类: Linux 标签:

ab测试swoole和ngixn+php-fpm对比

2015年10月29日 3 条评论

最近由于公司项目需要,要将一些原来的业务提取分离出来做成服务的模式供调用,目前我们的解决方案是使用韩天峰大大的swoole框架来解决这个问题。swoole这个框架虽然不算是一个新的东西,但是目前使用的其实还真不太多,在上一家做游戏的公司,我接触到了swoole,但是一致没有机会实用,所以一直不知道这个东西到底有多犀利。公司有几位做php的同事,不过他们一方面公司的项目比较近,需要他们去做,另一方面以前我对swoole也有过简单的接触,实话是说挺喜欢这个东西的。又花了近一周的时间来了解swoole,今天用swoole写的一个短信发送的项目就基本完成了,就用ab做了一些swoole测试,同时做了一些nginx的测试。swoole卓越的性能让我惊呆了,发现php原来也可以做这么强大多工作,感觉php的春天来了!

说了这么多废话,看一下swoole和nginx的性能测试对比吧。我这里就只给出ngixn测试的脚本代码,就是一个简单的mysql插入,而swoole那边是swoole框架搭建的一个server,用来处理收到的各种请求多,里面包含http协议头部和body部分的解析,以及请求的验证,最终是短信记录和发送。不过由于发送短信要收费,所以测试时把发送短信功能去掉,只有入库纪录功能。

nginx测试脚本

<?php
$link = mysql_connect("127.0.0.1","root","") or die(mysql_error());
mysql_set_charset("utf8");
mysql_select_db("sql_test");
$sql = "INSERT INTO ab_test (`msg`,`record_time`) VALUE(\"".uniqid()."itest\ ",\"".date("Y-m-d H:i:s”).”\”)”;
$result = mysql_query($sql);
var_dump($result);

swoole部分的代码由于设计公司业务和也比较多就不上了,只贴出nginx和swoole pk的测试数据了。

测试环境

os:centos7(mac pro的虚拟机里面装的centos7)

mem:4G(虚拟设置的4G,free查看时发现不到4G)

disk:固态硬盘(其实这里的影响最大是mysql的写入)

nginx 环境下就是上面的代码,但是swoole 是验证请求以后直接返回,由于我们swoole里面mysql和http请求都是异步处理,所以返回速度要比阻塞的nginx+fpm效率要高很多(nginx并不阻塞,关键是fpm和mysql).

10000请求1000并发

nginx的表现

nginx_10000_1000

swoole的表现

swoole_10000_1000

10000请求1500的并发

nginx的表现

nginx_10000_1500

swoole的表现

swoole_10000_1500

测试数据不难看出nginx+php-fpm模式在1000并发的时候就有数百多失败请求你,而且请求时间 用了75秒,而swoole只有12秒。我们再看一下qps参数(Requests per second)参数,由于1500的时候nginx此时全部返回500已经不可用,那就拿1000并发来看,nginx的在100多,swoole的达到了800多,而swoole在1500并发时候QPS也是800+。1500并发的时候虽然用时都差不多,但是nginx这使用失败率超过97%了,这个失败nginx几乎不可用,而swoole还是出色完成了任务。你可能会说swoole是直接返回,异步处理中可能也有失败的吧?很遗憾,我对比了数据表纪录,swool以100%的完成率全部完成了任务。

当然就目前来看1500还不是swoole的瓶颈,我在做2000并发测试时ab工具出了一些问题就没有再做。如果以后有机会就把它补上!

swoole,phper的春天!

分类: PHP 标签: ,

PHP扩展开发之扩展函数

2015年9月22日 没有评论

本来想着看了前面的文章,大家对php扩展开发中写自己的扩展函数会有一个很清晰的了解.但后来自己又梳理了一番,发现有几个地方可能还是需要展开一下的,这样会对扩展函数的开发有一个更深层次的理解.首先我们先来写一个简单的求和的php扩展函数.


PHP_FUNCTION(king_sum){
 long num1,num2,sum;
 if(zend_parse_parameter(ZEND_NUM_ARGS() TSRMLS_CC,"ll",&num1,&num2) == FAILURE){
 RETURN_FALSE();
 }
 sum = num1+num2;
 RETURN_LONG(sum);
}

代码很简单,接受2个参数,然后求和返回结果.接下来我们看看这里面都用到那些知识点.

PHP_FUNCTION宏

虽然上一篇在GDB里面已经展开说过这个宏了,这里还是要再展开一下,多了一些东西.


//main/php.h
#define PHP_FUNCTION ZEND_FUNCTION

//Zend/zend_API.h
#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )
#define ZEND_FN(name) zif_##name
#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

//Zend/zend.h
#define INTERNAL_FUNCTION_PARAMETERS int ht, zval *return_value, zval **return_value_ptr, zval *this_ptr, int return_value_used TSRMLS_DC

上面PHP_FUNCTION(king_sum)最终的展开结果是”void zif_king_sum(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)”,除了函数名没有其他的变化(如果你对宏不是很清楚,可以google一下C语言宏).宏展开的过程这里面就不再多说了,今天注意一下宏”INTERNAL_FUNCTION_PARAMETERS”,里面几个参数是什么意思哪?我们来看一下官方的文档.

INTERNAL_FUNCTION_PARAMETERS
名称和类型 描述 访问宏
int ht 用户实际传递参数的数量 ZEND_NUM_ARGS()
zval *return_value PHP 变量的指针,可填充返回值传递给用户。默认值是 IS_NULL RETVAL_*, RETURN_*
zval **return_value_ptr 当返回引用时,PHP 将其设为变量的指针。不建议返回引用。
zval *this_ptr 假如这是一个方法调用,其指向存放 $this对象的 PHP 变量。 getThis()
int return_value_used 指示返回值是否会被调用者使用的标志。

这几个参数的作用上面已经说的很清楚了.ht这个参数我们在解析参数时会遇到,前面的几篇文章里面有传参时,我们都会看到ZEND_NUM_ARGS()宏,return_value参数是我们目前写扩展函数中要经常使用到的,下面会在函数返回值时候提到操作相关函数的宏.this_ptr是调用类的时候才会用到的,再开始了解PHP面向对象相关的知识以前,我们是很少会遇到它的.return_value_used是内核传递进来的一个参数,脚本里面调用函数的时候返回值是否使用.

从PHP_FUNCTION这个宏可以看出,扩展函数展开以后都是void类型的,而且函数声明中我们也就看到一个PHP函数传参相关的,其余都是和返回相关的。由于前面用了一整篇来写扩展函数的传参,所以这里我们就不在展开,没有看到的朋友可以回头看一下php扩展开发之传参.

变量修改

在最初的 文章php弱类型的实现中,我们就已经知道了php中的变量最终是通过zval来存储的,相信通过这几篇文章中的例子,大家也可以看出来一下。php在创建变量的时候需要申请内存空间和一些初始化。在申请空间的时候,php提供了一套自己的函数,通过“emalloc,ecalloc,erealloc,efree”这些函数申请的内存,php会进行自己的垃圾回收,有php自身的一套垃圾回收机制.就算你忘记efree了,在请求接受以后这部分内存也会被释放掉.但是你需要注意的是如果你的php-fpm是以静态模式运行的时候,php模块初始化部分的内存是不会释放掉的,这对高并发的网站来说有效的减少了php内存的申请和释放的次数,大大的提高了php的运行效率。但是如果你的内存只有1G而且跑的还有其他运行程序,那么你以这种方式来跑php可能是灾难性的,很容易就把系统的内存资源耗尽。同时需要注意的有使用emalloc申请的内存要用efree去释放,malloc申请的内存要用free去释放。因为efree释放的内存情况上面它是把占用的内存交还给了php的内存管理,方便其他系统调用。关于php的内存管理这里就不在展开,以后如果有机会,会再详细的谈一谈。

zval的内存申请和初始化时,php也提供了几个相关的宏来方便我们操作:


//Zend/zend.h
#define MAKE_STD_ZVAL(zv) \
 ALLOC_ZVAL(zv); \
 INIT_PZVAL(zv);

#define ALLOC_INIT_ZVAL(zp) \
 ALLOC_ZVAL(zp); \
 INIT_ZVAL(*zp);

#define INIT_PZVAL(z) \
 (z)->refcount__gc = 1; \
 (z)->is_ref__gc = 0;

#define INIT_ZVAL(z) z = zval_used_for_init;

//Zend/zend_gc.h
#define ALLOC_ZVAL(z) \
 do { \
 (z) = (zval*)emalloc(sizeof(zval_gc_info)); \
 GC_ZVAL_INIT(z); \
 } while (0)

用这些php提供的宏我们可以很容易的创建出一个变量,那么怎么去给这些变量赋值那?

zval要处理所有php变量,其最终存储字符串/数字/对象/数组等数据是靠的zvalue_value联合体。


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;
 zend_ast *ast;
} zvalue_value;

如果我们自己直接对这个联合体进行赋值操作,那么我们需要根据真实情况进行复杂的操作,这是让人很头疼的问题。不过Zend内核也为我们提供了一系列的宏来解决这个问题。


#define ZVAL_RESOURCE(z, l) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_RESOURCE;\
 } while (0)

#define ZVAL_BOOL(z, b) do { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = ((b) != 0); \
 Z_TYPE_P(__z) = IS_BOOL; \
 } while (0)

#define ZVAL_NULL(z) { \
 Z_TYPE_P(z) = IS_NULL; \
 }

#define ZVAL_LONG(z, l) { \
 zval *__z = (z); \
 Z_LVAL_P(__z) = l; \
 Z_TYPE_P(__z) = IS_LONG; \
 }

#define ZVAL_DOUBLE(z, d) { \
 zval *__z = (z); \
 Z_DVAL_P(__z) = d; \
 Z_TYPE_P(__z) = IS_DOUBLE; \
 }

#define ZVAL_STRING(z, s, duplicate) do { \
 const char *__s=(s); \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = strlen(__s); \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, Z_STRLEN_P(__z)):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_STRINGL(z, s, l, duplicate) do { \
 const char *__s=(s); int __l=l; \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = __l; \
 Z_STRVAL_P(__z) = (duplicate?estrndup(__s, __l):(char*)__s);\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_EMPTY_STRING(z) do { \
 zval *__z = (z); \
 Z_STRLEN_P(__z) = 0; \
 Z_STRVAL_P(__z) = STR_EMPTY_ALLOC();\
 Z_TYPE_P(__z) = IS_STRING; \
 } while (0)

#define ZVAL_ZVAL(z, zv, copy, dtor) do { \
 zval *__z = (z); \
 zval *__zv = (zv); \
 ZVAL_COPY_VALUE(__z, __zv); \
 if (copy) { \
 zval_copy_ctor(__z); \
 } \
 if (dtor) { \
 if (!copy) { \
 ZVAL_NULL(__zv); \
 } \
 zval_ptr_dtor(&__zv); \
 } \
 } while (0)

宏函数虽然不少,但是其命名很规范,都是ZVAL_*的格式,很容易记忆的,都是对应相应的数据类型。我们来看一下用这些宏创建变量和赋值的例子吧。


zval *name,*age;

MAKE_STD_ZVAL(name);

MAKE_STD_ZVAL(age);

ZVAL_STRING(name,"King",1);

ZVAL_LONG(age,25);

函数调用

我们写php代码的时候,经常要调用自己写的其他函数.同理如果其他的我们需要调用其他的扩展函数时,我们没必要再把扩展函数代码赋值一份过来调用,Zend内核提供了相应的方法来让我们调用其他扩展函数。但是今天这个部分就先跳过去,后面我会在花费一个整篇文章来介绍这个话题。

函数体中都是C/C++代码,这也没什么好说的。如果你对这些知识欠缺,自己找些C/C++的书籍补一补。

函数返回

从上面PHP_FUNCTION宏展开中,我们可以看到扩展函数被声明成void,是没有返回内容的,但是扩展里面传递进来了2个指针,return_value和return_value_ptr.这不像我们写PHP函数,一般函数结果都是直接返回的,而扩展函数毕竟是要由C/C++开发出来,所以难免会类似C/C++的风格,熟悉C/C++编程的朋友对这些肯定不会陌生。这是Zend内核设计的模式,我们做为开发者也没有办法去更改他,只能适应这个模式。

在Zend/zend_API.h中,我们可以看到一些操作return_value相关的宏RETVAL_*和RETURN_*,其实RETURN_*是对RETVAL_*的封装.


#define RETVAL_RESOURCE(l) ZVAL_RESOURCE(return_value, l)
#define RETVAL_BOOL(b) ZVAL_BOOL(return_value, b)
#define RETVAL_NULL() ZVAL_NULL(return_value)
#define RETVAL_LONG(l) ZVAL_LONG(return_value, l)
#define RETVAL_DOUBLE(d) ZVAL_DOUBLE(return_value, d)
#define RETVAL_STRING(s, duplicate) ZVAL_STRING(return_value, s, duplicate)
#define RETVAL_STRINGL(s, l, duplicate) ZVAL_STRINGL(return_value, s, l, duplicate)
#define RETVAL_EMPTY_STRING() ZVAL_EMPTY_STRING(return_value)
#define RETVAL_ZVAL(zv, copy, dtor) ZVAL_ZVAL(return_value, zv, copy, dtor)
#define RETVAL_FALSE ZVAL_BOOL(return_value, 0)
#define RETVAL_TRUE ZVAL_BOOL(return_value, 1)

#define RETURN_RESOURCE(l) { RETVAL_RESOURCE(l); return; }
#define RETURN_BOOL(b) { RETVAL_BOOL(b); return; }
#define RETURN_NULL() { RETVAL_NULL(); return;}
#define RETURN_LONG(l) { RETVAL_LONG(l); return; }
#define RETURN_DOUBLE(d) { RETVAL_DOUBLE(d); return; }
#define RETURN_STRING(s, duplicate) { RETVAL_STRING(s, duplicate); return; }
#define RETURN_STRINGL(s, l, duplicate) { RETVAL_STRINGL(s, l, duplicate); return; }
#define RETURN_EMPTY_STRING() { RETVAL_EMPTY_STRING(); return; }
#define RETURN_ZVAL(zv, copy, dtor) { RETVAL_ZVAL(zv, copy, dtor); return; }
#define RETURN_FALSE { RETVAL_FALSE; return; }
#define RETURN_TRUE { RETVAL_TRUE; return; }

RETURN_*宏是对RETVAL_*宏的二次封装,而RETVAL_*宏是对return_value支持的操作,而return_value指针前面我们已经提过它是我们返回结果的重要手段之一.

模块载入函数

当然如果你这写到这里就以为结束了,然后去动手编译你的扩展,你会看到提示你这个方法不存在的。那是因为C扩展和PHP语言之间有一个zend_module_entry,里面有个zend_function_entry数组,这里面是记录我们当前扩展中有那些php函数的。如果你是手动创建的话需要自己来填写这个模块信息的变量,zend_module_entry内容很多,我们可以按照ext_skel工具生成的模板来填写。

zend_module_entry king_module_entry = {
 STANDARD_MODULE_HEADER, //标准的一个结构体头部
 "king",//模块名称
 king_functions,//zend_function_entry数组,保存扩展php函数的数组
 PHP_MINIT(king),//模块初始化函数,没有具体的需要做的可以为NULL
 PHP_MSHUTDOWN(king),//模块关闭调用函数,没有具体的需要做的可以为NULL
 PHP_RINIT(king), //模块运行前调用函数,跟在PHP_MINIT函数之后,
 PHP_RSHUTDOWN(king), /* Replace with NULL if there's nothing to do at request end, */
 PHP_MINFO(king),//
 PHP_KING_VERSION,//这个地方是我们扩展的版本,版本信息
 STANDARD_MODULE_PROPERTIES
};

const zend_function_entry king_functions[] = {
 PHP_FE(confirm_king_compiled, NULL) /* For testing, remove later. */
 PHP_FE(king_sum,king_sum_arginfo)//添加我们自己的函数,前面是调用函数,后面是参数信息
 PHP_FE_END /* Must be the last line in king_functions[] */
};

//king_sum函数参数信息
ZEND_BEGIN_ARG_INFO(king_sum_arginfo,1)
 ZEND_ARG_INFO(0,king_sum_num1)
 ZEND_ARG_INFO(0,king_sum_num2)
ZEND_END_ARG_INFO()

//
typedef struct _zend_arg_info {
 const char *name;
 zend_uint name_len;
 const char *class_name;
 zend_uint class_name_len;
 zend_uchar type_hint;
 zend_uchar pass_by_reference;
 zend_bool allow_null;
 zend_bool is_variadic;
} zend_arg_info;

//位置main/php.h
#define PHP_FE ZEND_FE
//位置Zend/zebd_API.h
#define ZEND_FE(name, arg_info) ZEND_FENTRY(name, ZEND_FN(name), arg_info, 0)
#define ZEND_FENTRY(zend_name, name, arg_info, flags) { #zend_name, name, arg_info, (zend_uint) (sizeof(arg_info)/sizeof(struct _zend_arg_info)-1), flags },

上面的代码中我们初始化了一个名为king_module_entry的zend_module_entry结构体,里面第三行的参数为当前模块函数数组。king_functions是一个zend_function_entry数组,我们可以使用PHP_FE宏往数组里面添加函数,第一个参数为函数名字,第二个参数为参数信息.参数信息是一个zend_arg_info数组:


typedef struct _zend_arg_info {
const char *name; //参数名称
zend_uint name_len;
const char *class_name;
zend_uint class_name_len;
zend_uchar type_hint;
zend_uchar pass_by_reference; //是否引用传递
zend_bool allow_null;
zend_bool is_variadic;
} zend_arg_info;

同样php也提供了一些宏来填充这些数组,下面我们来看一下这些宏:


#define ZEND_ARG_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_PASS_INFO(pass_by_ref) { NULL, 0, NULL, 0, 0, pass_by_ref, 0, 0 },
#define ZEND_ARG_OBJ_INFO(pass_by_ref, name, classname, allow_null) { #name, sizeof(#name)-1, #classname, sizeof(#classname)-1, IS_OBJECT, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_ARRAY_INFO(pass_by_ref, name, allow_null) { #name, sizeof(#name)-1, NULL, 0, IS_ARRAY, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_TYPE_INFO(pass_by_ref, name, type_hint, allow_null) { #name, sizeof(#name)-1, NULL, 0, type_hint, pass_by_ref, allow_null, 0 },
#define ZEND_ARG_VARIADIC_INFO(pass_by_ref, name) { #name, sizeof(#name)-1, NULL, 0, 0, pass_by_ref, 0, 1 },

#define ZEND_BEGIN_ARG_INFO_EX(name, _unused, return_reference, required_num_args) \
static const zend_arg_info name[] = { \
{ NULL, 0, NULL, required_num_args, 0, return_reference, 0, 0 },
#define ZEND_BEGIN_ARG_INFO(name, _unused) \
ZEND_BEGIN_ARG_INFO_EX(name, 0, ZEND_RETURN_VALUE, -1)
#define ZEND_END_ARG_INFO() };

ZEND_BEGIN_ARG_INFO是一个通用的结构体头部宏,要和ZEND_END_ARG_INFO配套使用.ZEND_ARG_INFO提供了否引用和参数名称的设置,pass_by_ref为是否引用,0为否,1为是,后面的name 为参数名称。第二个ZEND_ARG_PASS_INFO提供对参数是否引用的设置,参数具体值同上.后面的宏就不仔细介绍了,可以根据其名称推断出来其作用。

完成了这些,我们才算完成了一个完整的PHP扩展。我们可以按照前面文章进行编译和安装,然后进行测试了.

分类: PHP 标签: ,

php扩展开发之GDB调试

2015年8月19日 没有评论

俗话说的好"磨刀不误砍柴工",我们在开发扩展的时候,会像写C语言代码一样,经常会遇到程序core掉了.然而PHP脚本中常用的调试方式"echo,print_r,var_dump"却不能给我们什么大的帮助.今天我们就看看如何利用GDB来调试我们的php扩展程序?

为了方便我们使用gdb调试,最好在编译php程序的时候,把configure工具的”–enable-debug”参数启用,默认它是禁用的,因为它会产生很多方便我们调试的信息(注意在生产环境不要启用它,它会降低一部分php的性能).今天会主要说一下利用gdb进行断点调试,利用core文件调试已经不少的教程,所以我就不想再多做这些重复工作了.

前面的文章大家都看到如果声明一个php扩展函数,例如PHP_FUNCTION(count),这里如果我们直接利用cout函数来做断点,可能我们捕捉不到(这里我用了count做例子,但大家不要自己扩展里面这么做,因为count已经是php内建的扩展函数了,这样会引起不必要的问题).因为zend为了避免我们写的函数和C系统函数或这其他类库重名所以这里使用了PHP_FUNCTION宏进行声明,那么这个宏是什么样子哪?


#define PHP_FUNCTION ZEND_FUNCTION

#define ZEND_FUNCTION(name) ZEND_NAMED_FUNCTION( ZEND_FN(name) )

#define ZEND_FN(name) zif_##name

#define ZEND_NAMED_FUNCTION(name) void name(INTERNAL_FUNCTION_PARAMETERS)

上面我们提到的提到的”PHP_FUNCTION(count)“最终的展开结果是”void zif_count(int ht, zval *return_value, zval **return_value_ptr,zval *this_ptr, int return_value_used TSRMLS_DC)“,因此如果我们直接断点count可能就不会有结果的,这里我们需要用zif_count来断点就会捕捉到了.

如果我们使用的是一个第三方的扩展,我们不是很清楚里面有那些函数,或者修改过函数的前缀,已经不再是”zif”了.那么我们怎么获得我们需要的函数信息那?

我们可以使用nm工具来查看动态链接库里面的函数信息,就那我们上一篇那个交换2个数字的函数扩展为例子,看看如果使用nm吧.


nm king.so

//下面信息为nm摘取的部分信息

0000000000000e80 T zif_confirm_king_compiled
0000000000000f10 T zif_king_author
0000000000000fd0 T zif_king_call_system_func
0000000000000f30 T zif_king_exchange_number
0000000000000e60 T zm_activate_king
0000000000000e70 T zm_deactivate_king
0000000000001190 T zm_info_king
0000000000000e50 T zm_shutdown_king
0000000000000e40 T zm_startup_king

具体的信息如下图:

nm

 

看一下我们的扩展是否加入到php解释器中去那


php -m|grep king

//king

如果看到了”king”说明我们的扩展已经ok了,现在就做开始做gdb调试吧


cd /home/king/web_project/test/ext_king

gdb php

b zif_king_exchange_number

//Make breakpoint pending on future shared library load? (y or [n])

//这里用于这动态链接中,所以在我们php中是找不到这符号的,所以点y即可

//运行我们的php测试脚本

run ./exchange.php

//后面就是正常的gdb调试了

如此我们就可以清楚看到程序是怎么执行,更方便我们开发扩展程序.

如果我们有php程序core掉的文件,那么我们可以利用core文件进行调试.


gdb php -c core

#后面的可以和正常的gdb调试core文件一样,不过php提供了一个.gdbinit的帮助脚本,这个脚本在你下载的源文件的根目录下面,下面source的路径自行替换

source  ~/download/php-5.6.10/.gdbinit

zbacktrace

如果你需要更多.gdbinit脚本信息请自行google,或者去鸟哥的博客看看.

参考资料:

 如何调试PHP的Core之获取基本信息 

http://www.kuqin.com/language/20111022/313184.html

分类: PHP 标签:

php扩展开发之传参

2015年8月9日 没有评论

最近跟着服务器组同学学写C++,不是想转做C++开发,只是想借此机会锻炼一下自己.php的扩展不止可以使用原生的C,同样的可以使用C++来开发扩展来扩展我们的php.不过今天我们还不会谈C++扩展php,依旧还是原生的C来扩展我们的php.上一篇,我给了一个简单的扩展开发HelloWorld的例子.除了证明我们可以用C扩展php,它对我们日常工作可能要解决的问题来说毫无作用.今天我们来看一下php扩展传参是如果实现的,我们在日常的php开发当中写函数时要传参,同样我们看系统提供的函数中也有参数,但是我们在扩展函数的声明中是没有传参相关的参数的,那么它是如何实现的哪?

Zend API提供了解析参数的函数帮我们来解决这个问题:

ZEND_API int zend_parse_parameters(int num_args TSRMLS_DC, const char *type_spec, …)

ZEND_API int zend_get_parameters(int ht, int param_count, …)

我这里列了2个函数,zend_parse_parameters和zend_get_parameters,你可以在Zend/zend_API.c中找到他们.今天我们主要了解的是zend_parse_parameters,应为这是现阶段php5扩展中开发主要的解析参数的函数,而zend_get_parameters是针对php5之前的扩展用到的.需要你需要扩展老版本的php或者兼容旧的版本,请自行google翻阅资料,我个人觉得其意义不是很大,这里就不再多加探讨了.

zend_parse_parameters有2个主要参数,还有若干可变的参数,可变参数的作用就是接受我们php脚本中传递给扩展的参数.我们先看看这2个主要参数的作用吧:

int num_args就是本函数参数的个数,const char *type_spec是一个用于格式化的字符串,类似C里面的printf,不过他们2个却是完全不一样的.

参数   代表着的类型
b   Boolean
l   Integer 整型
d   Floating point 浮点型
s   String 字符串
r   Resource 资源
a   Array 数组
o   Object instance 对象
O   Object instance of a specified type 特定类型的对象
z   Non-specific zval 任意类型~
Z   zval**类型
f   表示函数、方法名称,PHP5.1里貌似木有... ...
下面的列表给出这些参数对应C里面的那些数据类型
参数  对应C里的数据类型
b   zend_bool
l   long
d   double
s   char*, int 前者接收指针,后者接收长度
r   zval*
a   zval*
o   zval*
O   zval*, zend_class_entry*
z   zval*
Z   zval**
后面跟的参数就是用来存储格式化字符串里面所表示的php传递的参数,需要注意的是字符串类型要占用2个参数,一个用来存储字符串,一个用来存储字符串长度.例子这里就不再单独的写了,细心的读者会发现上一篇扩展开发HelloWorld已经有介绍了.
今天我们写一个引用传参的例子,当初我在这个地方也纠结了好久,具体的是我们写一个互换2个数字的函数.
首先我们先用php写一下这个函数(注释部分为脚本输出):
<?php
$x = 1;
$y = 2;
exchange_num($x,$y);
echo $x."\n".$y."\n";
function exchange_num(&$a,&$b){
$c = $a;
$a = $b;
$b = $c;
}
//2
//1
这里我们使用了&符号来使用类似的功能,那么我们用扩展如何实现类似的引用哪?
PHP_FUNCTION(king_exchange_number){
long num1,num2;
zval *arg1,*arg2;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC,"zz", &arg1, &arg2) == FAILURE){
RETURN_NULL();
}
num1 = Z_LVAL_P(arg1);
num2 = Z_LVAL_P(arg2);
php_printf("num1:%ld,num2:%ld\n",num1,num2);
ZVAL_LONG(arg1,num2);
ZVAL_LONG(arg2,num1);
RETURN_NULL();
}
细心的朋友会发现我没有使用long类型来接受参数,而是换成zval *来接受.最初的时候我也试图利用long来接受数据,事实上我们可以用long来接受参数,我们可以读取参数里面的值,也可以改变参数的.但是我们却无法改变外部传进来参数的值,因为参数是局部变量,它在函数执行结束以后就会被释放掉,所以我们即使改变了这些用来接受参数的变量的值,其外部参数还是不会变化的.那么如果才能做到改变外部的参数哪?答案当然是指针,只有我们传递来的是一个指针的时候,我们才可以改变外部的参数值.但是我们回顾上面格式化字符串对照表里面,没有指针,但是注意看z是对应任意类型的,所以我们这里使用z.
在你当前的zend_function_entry数组中加入(以后的例子中会省略掉这一部分,读者自行添加即可)
PHP_FE(king_exchange_number,NULL)
然后保存重新编译扩展.然后我们在用我们写的扩展使用上面的功能(注释部分为脚本的输出结果).
<?php
$num1 = 11;
$num2 = 89;
king_exchange_number($num1,$num2);
echo "\$num1:$num1\n";
echo "\$num2:$num2\n";
//$num1:89
//$num2:11
到此相信你对php扩展传参也有个大致的了解了.文中的扩展在php-5.10中运行正常,如有疑问可以留言.

php扩展开发之”Hello World!”

2015年7月21日 2 条评论

最近在看《PHP扩展开发及内核应用》,书的内容不是特别多,但感觉都是精华,值得大家去一读的.这个系列的文章也是自己的读书的笔记或者一点心得吧,会把自己遇到的问题和踩过的坑都会提出来,大家可以引以为鉴,文章的不足和错误也请大家指出来.PHP做为现在最火的脚本语言之一,在Web方面更是当之无愧的王者,吸引大家使用PHP的原因有很多,而PHP的社区也是非常活跃和成功的,目前PHP7也预计要在十月份出来正式版本的.

其实PHP的扩展也是十分简单和便捷的,当然你在考虑用C/C++扩展你的PHP之前,建议还是考虑一下这么做是必须的或者说是必要的吗(用C/C++扩展你的PHP这篇文章开头关于扩展PHP的动机还是建议大家看一下的)?

现在我们将进入今天的整体写一个简单的整体,开发我们自己的PHP扩展.我下载的PHP版本是PHP5.6.10,也是最新版的,需要注意的是这个系列文章中的PHP都是PHP5以后版本的,之前的都是版本Zend engine都是1而PHP5之后Zend engine升级到2,因此如果你要是还在维护PHP5之前的版本,那么这个系列文章对您的帮助可能不是很大.

PHP源码提供了一个非常便捷的帮助大家创建自己的扩展工具,在ext目录下面大家可以看到它–“ext_skel”,在windows下面替代的工具是”ext_skel_win32.php”.

我们来看一下ext_skel有那些参数


./ext_skel --extname=module [--proto=file] [--stubs=file] [--xml[=file]]
 [--skel=dir] [--full-xml] [--no-help]

--extname=module       module is the name of your extension
 --proto=file          file contains prototypes of functions to create
 --stubs=file          generate only function stubs in file
 --xml                 generate xml documentation to be added to phpdoc-svn
 --skel=dir            path to the skeleton directory
 --full-xml            generate xml documentation for a self-contained extension(not yet implemented)
 --no-help             don't try to be nice and create comments in the code and helper functions to test if the module compiled

这里面的extname是指定我们自己扩展的名称,proto是指定包含我们想要创建的函数文件,一般文件是def格式的.

这里我们创建自己的hellworld.def文件,内容如下


void hello_world(string name)

下面我们只需要执行下面的命令,就完成了我们扩展的大部分工作了.


./ext_skel --extname=hello_world --proto=helloworld.def

Creating directory hello_world
Creating basic files: config.m4 config.w32 .gitignore hello_world.c php_hello_world.h CREDITS EXPERIMENTAL tests/001.phpt hello_world.php [done].

To use your new extension, you will have to execute the following steps:

1. $ cd ..
2. $ vi ext/hello_world/config.m4
3. $ ./buildconf
4. $ ./configure --[with|enable]-hello_world
5. $ make
6. $ ./sapi/cli/php -f ext/hello_world/hello_world.php
7. $ vi ext/hello_world/hello_world.c
8. $ make

Repeat steps 3-6 until you are satisfied with ext/hello_world/config.m4 and
step 6 confirms that your module is compiled into PHP. Then, start writing
code and repeat the last two steps as often as necessary.

上面的第1行是我们执行的命令,后面的脚本自动生成的内容,而我们开发扩展基本就按照这个流程来完成的.下面我们先去完成我们的hello_world函数去,然后我们的扩展工作基本就完成了.

进入ext/hello_world目录,编辑hello_world.c文件,这是我们扩展的主要文件了,只要我们的扩展内容不是特别多,我们可以完全把函数放在这里面的来完成.找到系统帮我们生成的代码中的”PHP_FUNCTION(hello_world)”,然后我们在此完成我们的hello_world功能.


PHP_FUNCTION(hello_world)
{
char *name = NULL;
int argc = ZEND_NUM_ARGS();
int name_len;

if (zend_parse_parameters(argc TSRMLS_CC, "s", &name, &name_len) == FAILURE)
return;

//php_error(E_WARNING, "hello_world: not yet implemented");
php_printf("Hello World!\n");
php_printf("Hello %s!\n",name);
RETURN_NULL();
}

其实我们就动了4行代码,注释掉php_error,然后和后面加上的3行代码.至此我们的功能就全部实现了.剩下就是按照扩展了.但是如果你去hello_world.c文件里面代码,你会发现ext_skel帮助我们生成很多代码,里面还是有一些东西是需要我们注意一下.

我们来看一下紧跟着hello_world函数的代码.


/* {{{ hello_world_functions[]
 *
 * Every user visible function must have an entry in hello_world_functions[].
 */
const zend_function_entry hello_world_functions[] = {
 PHP_FE(confirm_hello_world_compiled, NULL) /* For testing, remove later. */
 PHP_FE(hello_world, NULL)
 PHP_FE_END /* Must be the last line in hello_world_functions[] */
};
/* }}} */

/* {{{ hello_world_module_entry
 */
zend_module_entry hello_world_module_entry = {
 STANDARD_MODULE_HEADER,
 "hello_world",
 hello_world_functions,
 PHP_MINIT(hello_world),
 PHP_MSHUTDOWN(hello_world),
 PHP_RINIT(hello_world), /* Replace with NULL if there's nothing to do at request start */
 PHP_RSHUTDOWN(hello_world), /* Replace with NULL if there's nothing to do at request end */
 PHP_MINFO(hello_world),
 PHP_HELLO_WORLD_VERSION,
 STANDARD_MODULE_PROPERTIES
};
/* }}} */

“const zend_function_entry hello_world_functions”这个数组定义了我们扩展里面包含那些函数,当初我没有使用ext_skel的proto参数生成,就没有把我自己写的函数加入到hello_world_functions.导致我在编译安装完扩展以后,在PHP代码里面使用我在扩展里面写的函数的时候,就一直报错”Call to undefined function”,还是google好久才找到问题所在.所以如果你不是用ext_skel的proto参数生成的,在编译安装之前要注意一下这里.下面"zend_module_entry hello_world_module_entry "定义是hello_world模块.关于这些内容具体解释可以参考《PHP扩展开发与内核应用》第5章1节和4节的一些内容.

配置config.m4

现在如果我们要安装我们的扩展,还需要配置扩展目录下面的config.m4文件啦.

dnl PHP_ARG_WITH(hello_world, for hello_world support,
dnl Make sure that the comment is aligned:
dnl [ --with-hello_world Include hello_world support])

dnl Otherwise use enable:

PHP_ARG_ENABLE(hello_world, whether to enable hello_world support,
dnl Make sure that the comment is aligned:
[ --enable-hello_world Enable hello_world support])

dnl是m4文件的注释, 根据需要去掉相应的注释,PHP_ARG_WITH是我们在configure时”–with-xx”,此种方式一般依赖一些库的.而PHP_ARG_ENABLE是”–enable-xx”的方式,一般都是可以单独编译安装的,不依赖任何其他库支持的.我们这里选择PHP_ARG_ENABLE即可.

安装扩展

下面就是编译安装扩展了,如果你做过相关的工作就可以跳过这个小节的直接动手去尝试了.


phpize

./configure

make

sudo make install

在你的php.ini最后加上


extension=hello_world.so

//如果你的扩展是zend的,那需要改成zend_extension即可

重启你的apache或者php-fpm进行测试即可了.

测试扩展

编写一个测试的php脚本进行一下测试,查看我们的扩展是否安装成功.


<?php
hello_world("king");

?>

//最终输出如下,说明我们的扩展已经正常工作了

Hello World!
Hello king!

我们的hello world扩展开发也完成了.

 

php弱类型的实现

2015年6月30日 3 条评论

php是一门弱类型的语言,这基本是为人所共知的.但是php是用C语言编写的,C语言是一门强类型的语言,而由C语言开发而来的php没有像C++/Java这些延续C语言的强类型,那php是如何实现这个哪?

PHP在声明和使用变量的时候,并不需要显示的指明其数据类型,这是php弱类型带来的便捷(当然也有性能的损耗),但这并不是说PHP中没有类型的.在PHP中,存在8种变量类型,我们大致可以分为3类

标量类:boolean、integer、float(double)

复合类型:array、object

特殊类型:resource、NULL

上面我们谈了PHP中的类型,只是为了方便我们清楚PHP也是有类型这个概念的.如果我们想要弄清楚上面的问题,那么我们就需要看看PHP是如何实现这些变量的定义和存储的.

变量存储结构

PHP把变量存储到zval结构体中,zval结构体定义在Zend/zend_types.h文件中


typedef struct _zval_struct zval;

不难看出zval实际上面是struct _zval_struct结构体,_zval_struct定义在Zend/zend.h


struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
zend_uint refcount__gc;
zend_uchar type; /* active type */
zend_uchar is_ref__gc;
};

PHP使用这个结构体来存储所有用户变量.当然需要注意的是,PHP在存储变量时是存储在PHP用户空间(堆内存中),所以我们在PHP中可以把函数中的变量直接返回.而我们在C语言中有不少变量是声明在栈中的,如果我们直接把变量返回,那么可能会引发stack overflow的,特别是我们这边先接触PHP在回过头来看C的同学特别需要注意.PHP对堆内存有自己的内存管理和垃圾回收机制,所以一般我们PHP程序员大部分都是很少注意内存的消耗的.建议在程序比较占内存的时候,使用unset手动释放内存,这样会减轻服务器的资源压力.

zval结构体中有四个字段,其含义分别为:

属性名                含 义                默 认值
refcount__gc  表示引用计数         1
is_ref__gc        表示是否为引用       0
value                  存储变量的值
type                   变量具体的类型
目前对我们本文来说涉及到的是value和type,下面我们来看一下这2个字段.

变量类型

zval结构体的type字段就是我们今天问题的关键了,PHP就是通过存储时把字段的类型放在这个type字段里面来实现的弱类型的.type的值可以为: IS_NULL、IS_BOOL、IS_LONG、IS_DOUBLE、IS_STRING、IS_ARRAY、IS_OBJECT和IS_RESOURCE 之一.从字面上就很好理解,他们只是类型的唯一标示,根据类型的不同将不同的值存储到value字段。 除此之外,和他们定义在一起的类型还有IS_CONSTANT和IS_CONSTANT_ARRAY。

变量的值存储

看前面在存储时,变量最终是存储在 zvalue_value联合体中的,其定义在Zend/zend.h头文件中.


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;
zend_ast *ast;
} zvalue_value;

这里使用联合体而不是用结构体是出于空间利用率的考虑,因为一个变量同时只能属于一种类型。 如果使用结构体的话将会不必要的浪费空间,而PHP中的所有逻辑都围绕变量来进
行的,这样的话, 内存浪费将是十分大的。这种做法成本小但收益非常大。

PHP正是通过上面几个结构体来实现的弱类型,如果您感觉还有写不清楚,可以看一下<深入理解PHP内核>这本书.