ようこそゲストさん

CPA-LABテクニカル

2008/01/28(月) 変数の代入は、値のコピーにあらず-PHP変数管理 (3)

前回の変数管理の基礎(PHPの参照とは何か) でシンボルテーブルについて説明した。これがわかっていないとPHPの変数管理はわからない。


今回は、もっとも基本的な変数の値の代入、について説明する。
code1
$a=$b

なぜ、こんな単純なことをいちいち説明しなければならないのか。

それは、

PHPの「値の代入」は、まず参照代入の形をとって、後に値をコピー

だからだ。いったん、参照代入をするので、話が少しややこしい。

1 まず、前回同様のテーブルから。$a='one';の状態。

figure1 $a='one';
シンボルテーブル
変数名格納庫(zval)No.
a#0001
格納庫(zval)
No.type
#0001onestring

2 次に $b=$a; とやってみる。

通常の値の代入なのだから、値がコピーされて、次のようになると想像しては「いけない」

この塗りつぶしは、追加された部分この塗りつぶしは、変化があった部分この塗りつぶしは、削除された部分

figure2
シンボルテーブル
変数名格納庫(zval)No.
a#0001
b#0002

格納庫(zval)
No.type
#0001onestring
#0002onestring
※こうはならない


以下のようになる。

figure3-1
シンボルテーブル
変数名格納庫(zval)No.
a#0001
b#0001

格納庫(zval)
No.typeis_ref
#0001onestring0
おや、これでは参照代入 $b = & $a; と同じではないか。このままだと、 $a='two'; としたら、$b の値も変わってしまうではないか。参照:変数管理の基礎(PHPの参照とは何か)

その疑問は的を射ている。しかし、PHPはちゃんと解決手段を持っている。上のzvalの表にいつの間にか加わったis_ref項目をみてほしい。内部に格納庫(zval)No毎に is_ref というフラグを持っていて、参照代入ならis_ref=1、 通常代入なら is_ref=0 とセットしている。
今回の場合は、 is_ref=0 である。
figure3-2
is_ref0参照代入されたことはない
1過去に参照代入されている

3 では、$a='two';としてみよう。


この時点でようやく、値がコピーされて以下のようになる。(上の「想像してはいけない」と同じ状態になる)

figure4
シンボルテーブル
変数名格納庫(zval)No.(zval)
a#0001
b#0002

格納庫(zval)
No.type
#0001twostring
#0002onestring

なぜ通常代入で七面倒なことをするのか-references counting

通常の代入であれば、最初っから値をコピーすればいいじゃん、というのは、未来にはそうなるかもしれないが、メモリという大切なリソースを効率的に使うための方策としてこのような形式となっている。通常代入時($b=$a時)に値をコピーしてしまうと、同じ内容のものをメモリに持っておくことになる。それは非効率だから、今後、違うことになるときまでは一緒にいましょうね、ということだ。さらに、違う値になることがないのであれば、値のコピーそのものがいらないということもあり得よう。
このような方式を copy-on-write(またはリファレンスカウンティングreferences-counting)*1 方式という。

参照代入の方が、常に高速で、効率的か?

参照代入とメモリの貴重さを知ると、どうしても、参照代入を多用したくなる。しかし、それは浅はかというものだ。むしろ、参照代入は、「何かと何かを結びつける」「ある変数名とある変数名を結びつける」という点において威力を発揮させるものである。多くの場合*2メモリの効率的運用はPHPに任してしまったほうが、楽だし、実際に効率がよくなろう。むしろ、理解不足のまま参照代入を使うと、かえって非効率、低速のプログラムを知らず知らずのうちに組んでしまうこともある。私も反省している。

参照代入の中途半端使いがダメな理由は、具体的には、ここの記事が参考になる。
参照を多用することの危険性のページの後ろの方。)

*1 : PHPの仕様からすれば、リファレンスカウンティングが最も適切にその方法を表しているだろう

*2 : そうでない例については、後日とりあげたい

unsetの働き

前回、unsetは、シンボルテーブルから変数名と格納庫(zval)No.のひも付きを消去するだけだと述べた。すると、通常代入の際、格納庫(zval)No.に実体のゴミが残ってしまう。

4 続いて unset($a); してみよう


figure5
シンボルテーブル
変数名格納庫(zval)No.(zval)
b#0002

格納庫(zval)
格納庫(zval)No.(zval)refcount
#0001two0
#0002one1
このように#0001番地のように、すでに unset された 元の$a の値がゴミとして残るのはまずい。そこで、PHPでは、格納庫(zval)No毎に、refcountというのを持っている。refcountはいくつの変数名と紐ついているか、という数字である。参照されるごとに増えていき、参照が解除されるごとに減っていく。これがゼロになると、その格納庫(zval)No.は解放される仕組みとなっている。したがって、ゴミはきちんと掃き掃除され、以下のようになる。

figure6
シンボルテーブル
変数名格納庫(zval)No.(zval)
b#0002

格納庫(zval)
格納庫(zval)No.(zval)refcount
#0002one1

次回は配列型(array)の管理方法について説明したい。


PHP変数管理の目次はこちら 

名前:  非公開コメント   

  • TB-URL  http://www.cpa-lab.com/tech/030/tb/
  • issetを正しく理解する。null値とunsetの違い-PHP変数管理 CPA-LABテクニカル spok
    nullほど人を惑わすものはないだろう。設定されてない値ってなんだ?unsetされた変数とは違うの?様々な疑問があろう。ここで、明確な理解をしておきたい。(PHPの場合)シンボルテーブルのおさらい以前に変数管理の基本として書いたけれども、PHPは、シンボル...