PHPのガベージコレクションを理解する為のメモ

PHPは動的に変数を扱う事ができる言語で、この動的に変数を操作するためにzvalというものがある。


動的に変数を扱う例として、

/path/to/dir/var.php

<?php
//変数aに数字の1を挿入する
$a = 1;
var_dump($a);

//変数aに文字列のhogeを挿入する
$a = "hoge";
var_dump($a);

//変数aに配列の値を挿入する
$a = array(1, 2, 3);
var_dump($a);

上記のコードのように、同じ変数名に型の異なる値を何度上書きしても、

$ php /path/to/dir/var.php
int(1)
string(4) "hoge"
array(3) {
  [0] =>
  int(1)
  [1] =>
  int(2)
  [2] =>
  int(3)
}

上記のようにエラーが発生せずに処理を終了させることができる。

このコードを静的な変数であるGo言語で書いてみると、

/path/to/dir/var.go

package main

import "fmt"

func main() {
	a := 1
	fmt.Println(a)

	a = "hoge"
	fmt.Println(a)
}

a = "hoge"を書いた時点で、エディタでエラーになってしまうので、上記のコードでやめ、実行してみると、

$ go run /path/to/dir/var.go
# command-line-arguments
./var.go:9:6: cannot use "hoge" (type string) as type int in assignment

整数型の変数に文字列型の値を挿入できないというエラーが発生する。


PHPではどのようにして変数の値の型の変更に対応しているのだろうか?

というわけで、PHPの変数の仕組みについて見ていく事にする。




GitHubのphp-srcリポジトリの内容からzvalについて見ていく事にする。

※以後の話はC言語のコードで話を進める


PHPの変数入門に記載されている最も基礎的な箇所はzvalというもので、下記の構造体になっている。

https://github.com/php/php-src/blob/master/Zend/zend_types.h#L305

struct _zval_struct {
	zend_value        value;			/* value */
	union {
		uint32_t type_info;
		struct {
			ZEND_ENDIAN_LOHI_3(
				zend_uchar    type,			/* active type */
				zend_uchar    type_flags,
				union {
					uint16_t  extra;        /* not further specified */
				} u)
		} v;
	} u1;
	union {
		uint32_t     next;                 /* hash collision chain */
		uint32_t     cache_slot;           /* cache slot (for RECV_INIT) */
		uint32_t     opline_num;           /* opline number (for FAST_CALL) */
		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     constant_flags;       /* constant flags */
		uint32_t     extra;                /* not further specified */
	} u2;
};

上のzvalの構造体は、PHPの変数そのものの値だけれども、zvalだけではどのような型(整数型、文字列型や配列型)は決まっておらず、当然値も決まっていない。

このzvalのメンバのzend_valueに値を格納する為の構造体を紐付ける。

https://github.com/php/php-src/blob/master/Zend/zend_types.h#L285

typedef union _zend_value {
	zend_long         lval;				/* long value */
	double            dval;				/* double value */
	zend_refcounted  *counted;
	zend_string      *str;
	zend_array       *arr;
	zend_object      *obj;
	zend_resource    *res;
	zend_reference   *ref;
	zend_ast_ref     *ast;
	zval             *zv;
	void             *ptr;
	zend_class_entry *ce;
	zend_function    *func;
	struct {
		uint32_t w1;
		uint32_t w2;
	} ww;
} zend_value;

例えば、整数の1を格納した変数であれば、

zend_valueの共用体(どれか一つ値をセット)でzend_long(PHPの整数型)に1をセットして、zendと紐付けることで、整数1の値が格納された変数ができることにになる。

文字列が格納された変数を用意したければ、zend_valueのzend_stringメンバに文字列の値(のポインタ)をセットし、配列であれば、zend_vaueのzend_arrayメンバに配列の値(のポインタ)をセットする。

ポインタの箇所は更に込み入るので、今回はポインタ周りは触れないことにする。


zvalとzend_valueを紐付けるという仕組みによって、冒頭のような一度作成した変数で何度も型を変更して値を挿入できるようになる。




今までの説明で変数のデータの持ち方はわかったけれども、

<php
$a = 1;

のようにある変数のシンボルがaである事を登録しておく仕組みがどこにも無い。


というわけで、次は変数のシンボルを登録し、利用する為の仕組みを見ていく。

PHP: Zend API: PHP のコアをハックする - Manualのページの変数の作成の節を読むと、他言語同様、シンボルテーブルを採用している。

※PHPではHashTableの仕組みを利用する

シンボルテーブル - Wikipedia

PHP: HashTable の扱い - Manual


変数の作成の流れとしては、

・$a用のメモリを確保する

・zvalを初期化する

・zend_valueを用意して、zend_longメンバに1をセットする

・zvalとzend_valueを紐付ける

・zvalのポインタ(メモリ上のどこにデータがあるか?を示すアドレス)を取得する

・Zend APIのZEND_SET_SYMBOLで作成したzvalのポインタのシンボルを"a"として登録する



これで$aという変数がメモリ上のどこの値を参照するか?が登録されたことになり、いつでも$aの値を確認できるようになった。


シンボルテーブルについての話は



O'Reilly Japan - コンピュータシステムの理論と実装にわかりやすい記載がある。