php变量的基本实现

PHP作为“世界上最好的语言”,我们都知道php是弱类型语言,即在使用过程中,可以任意改变变量的类型。这对于代码中的灵活性有极大的方便。php底层是由c语言去实现的,那么c语言作为强类型语言,是怎么实现php的这些特性?

变量的定义

我们先从变量的定义开始。在百度百科上,变量来源于数学,是计算机语言中能储存计算结果或能表示值抽象概念。变量可以通过变量名访问。意思就是说,变量是来存储值的。那么我们来看看在php中,变量的定义。

//php
//定义了变量名为a 变量值为 整数 1
$a=1;
//输出$a的值
echo $a;
//将变量值 字符串 aaaa 赋给了变量名a
$a='aaaa';

在php中,变量会在首次为其赋值时被创建,我们在使用中,可以随意改变变量的类型。示例中的代码,简单两行就完成了,变量的定义赋值和重新赋值。而在c语言中,变量则需要先创建才能使用,并且需要严格定义类型。

在php中变量的值由zval来表示

php7中,zval的结构定义如下。zval由,value、u1和u2组成。

struct _zval_struct {
    zend_value        value;            /* value */
    union {
        struct {
            ZEND_ENDIAN_LOHI_4(
                zend_uchar    type,         //存储具体类型
                zend_uchar    type_flags,
                zend_uchar    const_flags,
                zend_uchar    reserved)     /* call info for EX(This) */
        } v;
        uint32_t type_info;
    } u1;
    union {
        uint32_t     next;                 /* hash collision chain */
        uint32_t     cache_slot;           /* literal cache slot */
        uint32_t     lineno;               /* line number (for ast nodes) */
        uint32_t     num_args;             /* arguments number for EX(This) */
        uint32_t     fe_pos;               /* foreach position */
        uint32_t     fe_iter_idx;          /* foreach iterator index */
        uint32_t     access_flags;         /* class constant access flags */
        uint32_t     property_guard;       /* single property guard */
        uint32_t     extra;                /* not further specified */
    } u2;
};

typedef union _zend_value {
    zend_long         lval;             //整型
    double            dval;             //浮点型
    zend_refcounted  *counted;          //引用计数
    zend_string      *str;              //字符串类型
    zend_array       *arr;              //数组类型
    zend_object      *obj;              //对象类型
    zend_resource    *res;              //资源类型
    zend_reference   *ref;              //引用类型
    zend_ast_ref     *ast;              //抽象语法树
    zval             *zv;               //zval类型
    void             *ptr;              //指针类型
    zend_class_entry *ce;               //class类型
    zend_function    *func;             //function类型
    struct {
        uint32_t w1;
        uint32_t w2;
    } ww;
} zend_value;

变量弱类型的实现

在zval中,value存储具体的值或者值的指针,u1存储变量类型的相关信息,u2则存储优化信息。php根据u1.type的值的变化,取value中对应类型的值的指针,从而完成变量类型的表达。
在上述php代码中

  • $a=1;php生成新的zval,将zval.u1.v.type定义为整型,将1写入到zval.value.lval中。
  • echo $a;PHP在变量哈希表中,找到变量名为a的键,拿到对应的zval,根据zval.u1.v.type的类型读取对应zval.value中的值或者是对应的指针类型。
  • $a='aaa';php拿到变量a的zval,根据要赋予变量的值,来修改zval.u1.v.type的值,同时将zval.value中对应的存储字段进行赋值。

变量类型的转换

在php内核中定义了几个函数,当需要将变量转换成相同类型时,则会取调用对应的函数,进行变量的转换。比如在比较整型和字符串时,会根据规则,将整型转成字符串,或者是将字符串转成整型,在进行比较。

这里贴一下_zval_get_string_func 转换成字符串函数的源码

ZEND_API zend_string* ZEND_FASTCALL _zval_get_string_func(zval *op) /* {{{ */
{
try_again:
    switch (Z_TYPE_P(op)) {
        case IS_UNDEF:
        case IS_NULL:
        case IS_FALSE:
            return ZSTR_EMPTY_ALLOC();//被初始化成空字符串
        case IS_TRUE:
            if (CG(one_char_string)['1']) {
                return CG(one_char_string)['1'];
            } else {
                return zend_string_init("1", 1, 0);
            }
        case IS_RESOURCE: {//返回对应的资源号ID
            char buf[sizeof("Resource id #") + MAX_LENGTH_OF_LONG];
            int len;

            len = snprintf(buf, sizeof(buf), "Resource id #" ZEND_LONG_FMT, (zend_long)Z_RES_HANDLE_P(op));
            return zend_string_init(buf, len, 0);
        }
        case IS_LONG: {
            return zend_long_to_str(Z_LVAL_P(op));
        }
        case IS_DOUBLE: {
            return zend_strpprintf(0, "%.*G", (int) EG(precision), Z_DVAL_P(op));
        }
        case IS_ARRAY:
            zend_error(E_NOTICE, "Array to string conversion");
            return zend_string_init("Array", sizeof("Array")-1, 0);
        case IS_OBJECT: {
            zval tmp;
            if (Z_OBJ_HT_P(op)->cast_object) {
                if (Z_OBJ_HT_P(op)->cast_object(op, &tmp, IS_STRING) == SUCCESS) {
                    return Z_STR(tmp);
                }
            } else if (Z_OBJ_HT_P(op)->get) {
                zval *z = Z_OBJ_HT_P(op)->get(op, &tmp);
                if (Z_TYPE_P(z) != IS_OBJECT) {
                    zend_string *str = zval_get_string(z);
                    zval_ptr_dtor(z);
                    return str;
                }
                zval_ptr_dtor(z);
            }
            zend_error(EG(exception) ? E_ERROR : E_RECOVERABLE_ERROR, "Object of class %s could not be converted to string", ZSTR_VAL(Z_OBJCE_P(op)->name));
            return ZSTR_EMPTY_ALLOC();
        }
        case IS_REFERENCE:
            op = Z_REFVAL_P(op);
            goto try_again;
        case IS_STRING:
            return zend_string_copy(Z_STR_P(op));
        EMPTY_SWITCH_DEFAULT_CASE()
    }
    return NULL;
}

当你在php代码中,在一个值前(string) strval()或者是使用echo,将变量当作字符串处理时,就会用到这个函数。

  • 当变量类型是未定义undefined、空null或者是布尔false时,返回一个初始化的空字符串。
  • 当变量类型是布尔true,返回'1'
  • 当变量类型是资源,则返回 'Resource id #'+资源id
  • 当变量类型为整型,则返回这一串数字的字符串
  • 当变量类型为浮点数,则返回这一串数字的字符串
  • 当变量类型为数组,则返回字符串'Array'
  • 当变量类型为对象,不可转换,并报错 'Object of class %s could not be converted to string'
  • 当变量类型为引用,取出引用的zval,在继续转换
  • 当变量类型为字符串,则输出字符串。

类似的函数还有 zval_get_double、zval_get_long

变量的存储

php的变量存储在hashtable上,当调用一个函数或者类的时候,会创建新的hashtable,这也就是为什么没法直接在函数内使用外部定义的变量。因为它们分属两个作用域,一个是当前的(局部变量),一个是全局的(全局变量)。在函数内怎么使用全局变量呢,通过globals 关键字,在创建新的hashtable的时候,将局部变量表的值链接全局变量表,从而达到了,在函数内使用全局变量。

当在函数中使用global $a

变量的赋值和引用

赋值

对于整型、浮点型、布尔和NULL,由于占用空间小,在zval中直接存储。直接在进行赋值时,会创建2个zval。
字符串、数组、资源类型和对象会在赋值时,指向同一个value,等到变量的值被改变时,才会申请变量值的内存空间。

$a='1234';
$b=$a;

当将a的值赋给b时,变量a和b指向同一个字符串,并且zend_string的refcount 会记录当前被引用的次数。

字符串赋值

引用

在php7中,引入了zend_reference来处理。使得即使变量同时被引用和赋值,在内存中只存有一份字符串。
通过在zend_reference中的zval来进行一次引用的转发。

struct _zend_reference{
  zend_refcounted_h gc;
  zval val;
}

$a = '1234';//$a->zend_string(type=IS_STRING,recount_gc=1,is_ref_gc=0);
$c=$a;// $c,$a-> zend_string (type=IS_STRING,recount_gc=2,is_ref_gc=0);
$b=&$a;// $b,$a-> zval (type=IS_REFERENCE,recount_gc=2);
//$c-> zend_string (type=IS_STRING,recount_gc=2,);
字符串引用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342