Udemy で講座の販売を開始しました!
Udemy で「プログラミング学習の心得&HTTPの基礎」の動画講座を公開しました。
特別に 85% OFFのクーポンを発行します。
通常価格 10,800 円が 1,800 円で購入できます。
動画内でも説明していますが、初心者・初学者向けの内容です。
事前に自分の知りたい内容・興味のある内容か確認してからご購入ください。
はじめに
この記事を理解するには、変数、関数、クラスの基本的な知識が必要です。
PHP 内部で値の代入と値渡しがどのように処理されているのか見ていくことで理解を深めることが目的です。
PHP5 と PHP7 では内部構造が異なりますが、この記事では PHP5 の内部処理を表しています。
PHP の内部って何のこと? という方は下記の記事をご覧ください。
今回、下記の流れでまとめました。
第1章 値の代入
スカラー型の代入
配列型の代入
オブジェクト型の代入
第2章 値渡し
スカラー型の値渡し
配列型の値渡し
オブジェクト型の値渡し
私自身未熟ですので説明に誤りがあるかもしれません。
誤りがあれば、ご指摘ください ((_ _ (´ω` )ペコ
第1章 値の代入
値という意味について確認しましょう。
値とは、プログラミングなどで、変数などに格納されている、あるいは、一定の形式に則って記述した、具体的なデータや数値。
値とは整数や文字列などのことです。
PHPのコードを書いたことある方なら、既に値の代入をしています。
$a = 1;
このように =
を使って $a
という変数に 1
という値を格納することを 値の代入 と呼びます。
値の代入についてもう少し詳しく見ていきましょう。
$a = 1;
$a = 2;
$a = 3;
同じ $a
という変数に値を代入すれば、その都度 $a
の値は変わります。
上記の場合なら最終的に $a
には 3
という値が格納されています。
次の場合はどうでしょうか。
$a = 1;
$a = 2;
$b = 3;
$b = 4;
最終的に $a
には 2
という値が、$b
には 4
という値が格納されています。
次の場合はどうでしょうか。
$a = 1;
$b = $a;
$a
と $b
には 1
という値が格納されています。
$a
と $b
は同じ 1
という値ですが、これは別々の値です。
$b = $a
と代入したときに $a
にある 1
という値が複製されて、$b
に代入されます。
このときにPHP内部でどのように処理されているのか詳しく見ていくことで、代入時の挙動について理解を深めていきましょう。
値と言ってもいくつか種類(型)があります。
PHPには主に下記の型があります。
分類 | 型 |
---|---|
スカラー型 | 論理型(bool) |
整数型(int) | |
浮動小数点型(float) | |
文字列型(string) | |
複合型 | 配列型(array) |
オブジェクト型(object) |
スカラー型、配列型、オブジェクト型の代入について見ていきましょう。
スカラー型の代入
スカラー型(整数型)を代入すると、PHP内部ではどのように処理されるのか見ていきましょう。
$a = 1;
$b = $a;
$b = 2;
echo $a; // 結果:1
echo $b; // 結果:2
$a = $b
のように値を代入すると値の複製が行われるため、$a
と $b
は別々の値を持っています。
そのため、出力結果が異なります。
PHP内部ではどのようになっているのか1行ずつ見ていきましょう。
1行目(スカラー型の代入)
$a = 1;
PHP内部では下記のようになっています。
PHPは変数名と値を別々に管理しています。
変数名はスコープごとに管理しており、今回はグローバル変数ですので global symbol table
の中に $a
という変数名があります。
右側にある (int)1
は値に関する情報です。
今回は 1
という整数を代入したので、(int)1
となっています。
refcount
というのは、変数名がこの値をいくつ指し示しているかという情報です。
1行目では $a
という変数名しかこの値を指し示していませんので、refcount
は 1
になっています。
is_ref
は参照に関する情報で、参照されているときは 1
、参照されていないときは 0
です。
is_ref
は bool
値のため、1(true)
か 0(false)
の2値しかありません。
この記事では値の代入と値渡しのみで、参照の代入と参照渡しについては別の記事で説明します。
この記事内では参照はしませんので、is_ref
の値が 1
になることはありません。
2行目(スカラー型の代入)
$b = $a;
2行目では $b
に $a
を代入しています。
global symbol table
に $b
が追加され、$a
と同じ値を指し示します。
$a
と $b
の2つの変数名がこの値を指しているため、refcount
の値は 2
になります。
この記事を読んでいる方の中には、この動きに疑問を持った方もいると思います。
値を代入したときは値の複製が行われ、別々の値になると説明しました。
おそらく下記のようになると考えたのではないでしょうか。
PHPは**コピーオンライト(Copy-On-Write)**で効率よく複製を行っています。
コピーオンライトは複製する必要が生じたときに、複製されて書き換えが行なわれます。
$a = $b
のように代入したときは $a
も $b
も同じ値です。
同じ値なのでこの時点では複製しても無駄が生じるため、既存の同じ値を利用します。
どちらかの値に変更があったときに、はじめて値が複製されて書き換わります。
同じ値を指し示しているため違和感を感じますが、既存の値を利用しているだけで変更があれば複製されて別々の値になります。
コピーオンライトのため既存の値を利用するが、実際は別々の値と覚えておきましょう。
既存の値を利用するというのは $a = $b
のように複製が行われる、すなわちコピーオンライトが動作するときに限ります。
下記のように別々の変数に同じ 1
という値を代入しても、同じ値を指し示すことはありません。
$a = 1;
$b = 1;
PHP内部では下記のように別々の値を指し示します。
3行目(スカラー型の代入)
$b = 2;
$b
に 2
という値を代入しました。
$a
と $b
は別々の値になるため、値の複製が必要になります。
コピーオンライトのため、この時点で値が複製されて書き換えが行なわれます。
$b
という変数は (int)2
を指し示すようになります。
(int)1
は $a
しか指し示していませんので、refcount
は 1
になります。
1行ずつどのようになっているか見てきたため、値を代入したときに内部でどのように処理されているのか理解できたかと思います。
スカラー型代入のポイント
$a = $b
のようにスカラー型の値を代入したときは、値の複製が行われて別々の値になる- PHPでは変数名と値を別々に管理している
- PHP内部ではコピーオンライト方式を採用しており、複製時は既存の値を効率的に利用している
次は配列型の代入について見ていきましょう。
配列型の代入
配列型を代入すると、PHP内部ではどのように処理されるのか見ていきましょう。
$a = [1,2,3];
$b = $a;
$a[0] = 'a';
$b[2] = 'c';
var_dump($a);
var_dump($b);
var_dump
で $a
と $b
を表示すると下記のようになります。
// $a の表示結果
array(3) {
[0] => string(1) "a"
[1] => int(2)
[2] => int(3)
}
// $b の表示結果
array(3) {
[0] => int(1)
[1] => int(2)
[2] => string(1) "c"
}
表示結果からも $a
と $b
は別々の配列ということがわかります。
スカラー型の代入と同じく、$a = $b
のように代入すると配列が複製されます。
複製されるため $a
と $b
は別々の配列になり、異なる表示結果になります。
PHP内部ではどのようになっているのか1行ずつ見ていきましょう。
1行目(配列型の代入)
$a = [1,2,3];
配列を代入したときは、変数名 $a
は直接各値を指し示すわけではなく、配列 (array)
を指し示します。
配列のキーが各値を指し示します。
配列 (array)
自体に refcount
と is_ref
があります。
2行目(配列型の代入)
$b = $a;
代入したときは複製されるため $a
と $b
は別々の配列ですが、コピーオンライト方式のため、まだ複製はされません。
そのため、変数名 $b
も $a
と同じ (array)
を指し示します。
refcount
が 2
になります。
3行目(配列型の代入)
$a[0] = 'a';
$a
の配列キー 0
に a
という文字列を代入しています。
$a
の配列キーが指し示している値に変更があったため、このときに配列の複製が行われます。
変更があったのは、配列キー 0
が指し示していた (int)1
のみです。
$a
と $b
の配列キー 1
と 2
は同じ値です。
変更がない値を複製しても無駄が生じるため、複製はされず同じ値を指し示す。
それにあわせて refcount
の数が変わります。
4行目(配列型の代入)
$b[2] = 'c';
$b
の配列キー 2
に c
という文字列を代入したため、このときに値の複製と書き換えが行なわれます。
$b
の配列キー 2
は (stirng)2
を指し示します。
それにあわせて (int)3
の refcount
は 1
になります。
配列型代入のポイント
$a = $b
のように配列を代入したときは、コピーオンライトのため最初は同じ配列を指し示すが、配列の値に変更が時点で配列の複製が行われる。配列が指し示している各値もコピーオンライトのため、変更があった時点で複製されて書き換えが行われる- 変数名は配列を指し示し、配列自体にも
refcount
とis_ref
がある - 配列もコピーオンライト方式を採用しており、複製時は既存の配列を効率的に利用している
次はオブジェクトの代入について見ていきましょう。
オブジェクト型の代入
オブジェクト型の代入を理解するには、オブジェクトについての理解が必要です。
クラスやインスタンスなどについて理解されていない方は、下記の記事の第1章をご覧ください。
class foo {
public $bar = 1;
public $baz = "a";
}
$a = new foo;
$a->bar = 2;
$b = $a;
$b->bar = 3;
$b = 4;
$c = clone $a;
$c->bar = 5;
$d = $c->bar;
$e = new foo;
オブジェクト型を代入すると、PHP内部ではどのように処理されるのか見ていきましょう。
1行目(オブジェクトの代入)
$a = new foo;
1行目は new
でインスタンス化してオブジェクト(インスタンス)を $a
に代入しています。
PHP内部で変数名 $a
はオブジェクト (object)foo
を指し示します。
オブジェクトには handle
と呼ばれる番号があり、オブジェクトごとに割り当てられます。
現時点でオブジェクトは1つしかありませんので、handle
は 1
になります。
新たにオブジェクトを作成すれば、そのオブジェクトの handle
は 2
になります。
この handle
の番号は var_dump
で確認することも可能で、#1
のように表示されます。
またオブジェクト自体にも refcount
や is_ref
があります。
各プロパティは配列で管理しており、それぞれのプロパティ名は各値を指し示しています。
各値は今までと同じように refcount
や is_ref
があります。
定義した foo
クラスから各値を指し示しているため、各値の refcount
は 2
になります。
2行目(オブジェクトの代入)
$a->bar = 2;
$a
にあるオブジェクトの bar
プロパティの値に 2
を代入しました。
bar
プロパティは (int)2
を指し示します。
それにあわせて (int)1
の refcount
は 1
になります。
3行目(オブジェクトの代入)
$b = $a;
$b
に $a
を代入しました。
コピーオンライトのため、$b
は $a
と同じオブジェクトを指し示します。
(object)foo
の refcount
は 2
になります。
4行目(オブジェクトの代入)
$b->bar = 3;
$b
にあるオブジェクトの bar
プロパティの値に 3
を代入しました。
$b
と $a
は同じオブジェクトを指し示しています。
同じオブジェクトのため、$a
のオブジェクトの bar
プロパティの値も 3
になります。
スカラー型のときは変数名が直接指し示している値に変更があったとき、配列型のときは変数名が直接指し示している配列の値に変更があったときに複製されました。
オブジェクト型の場合は、各プロパティの値に変更があっても、(object)foo
自体は複製されません。
PHPでは clone
キーワードを明示しない限り、オブジェクト自体は複製されません。
clone
キーワードを明示したオブジェクトの複製についてはこの後説明します。
ここで注目すべきはオブジェクトの各プロパティが指し示している値に変更があっても、オブジェクト自体は複製されないということです。
変数名の $a
と $b
が直接指し示している (object)foo
に変更がなければ、複製はされないということです。
例えば、$b
に 4
を代入するなど、変数名が直接指し示すものに変更があれば、その時点で複製されて書き換えが行われます。
余談ですが「PHPのオブジェクトはデフォルトでは参照による代入または参照渡しになる」と誤解される方もおります。
$a = $b
のようにオブジェクトの代入をしたときは、同じオブジェクトを指し示し、各プロパティを変更してもオブジェクト自体が複製されないため、参照による代入または参照渡しだと誤解されるのかと思います。
PHPのオブジェクトはデフォルトで参照代入や参照渡しではありません。
参照に関してこの記事とは別に投稿したいと思います。
5行目(オブジェクトの代入)
$b = 4;
先ほども説明しましたが、変数名 $b
が直接指し示すものに変更があったため、このときに値が複製され書き換えが行われます。
$b
は (int)4
を指し示します。
それにあわせて (object)foo
の refcount
は 1
になります。
6行目(オブジェクトの代入)
$c = clone $a;
3行目では $b = $a
のようにオブジェクトを代入しましたが、同じオブジェクトを指し示し、オブジェクトは複製されませんでした。
オブジェクトを複製するには、clone
キーワードを使います。
$a
のオブジェクトの複製を $c
に代入したため、$a
と $c
は別のオブジェクトになります。
別のオブジェクトのため、 $c
のオブジェクトの handle
は 2
になります。
各プロパティの値は $a
のオブジェクトと同じ値を指し示すため、それにあわせて (string)'a'
と (int)3
の refcount
の数が変わります。
コピーオンライトのため、同じ値を指し示していますが、値に変更があった時点で複製されて書き換わります。
7行目(オブジェクトの代入)
$c->bar = 5;
$c
のオブジェクトの bar
プロパティの値に 5
を代入しました。
bar
プロパティは (int)5
を指し示します。
それにあわせて (int)3
の refcount
は 1
になります。
8行目(オブジェクトの代入)
$d = $c->bar;
$c
のオブジェクトの bar
プロパティの値を $d
に代入しています。
このように代入したときは値が複製されて別々の値になりますが、コピーオンライト方式のためこの時点では同じ値を指し示します。
$d
または $c->bar
の値に変更があった時点で複製されて書き換わります。
9行目(オブジェクトの代入)
$e = new foo;
$e
に foo
クラスのオブジェクト(インスタンス)を代入しています。
新しいオブジェクトですので $e
のオブジェクトの handle
は 3
になります。
各プロパティの値は、foo
クラスと同じ値を指し、それにあわせて refcount
の数も変わります。
1行ずつどのようになっているか見てきたため、オブジェクトを代入したときに内部でどのように処理されているのか理解できたかと思います。
オブジェクト型代入のポイント
$a = $b
のようにオブジェクト型を代入したときは、コピーオンライトのため最初は同じオブジェクトを指し示すが、$a
または$b
に別の値を代入するなど、変数名が直接指し示しているものに変更があった時点で複製されて書き換えが行われる- オブジェクトの各プロパティの値に変更があっても、オブジェクト自体は複製されない
- オブジェクトを複製するときは、
clone
キーワードを使う - オブジェクトには
handle
と呼ばれる番号が割り当てられている
次は値渡しについて見ていきましょう。
第2章 値渡し
関数やメソッドの引数に値を渡すことを 値渡し といいます。
関数を利用したことがあるなら、既に値渡しを使っているかと思います。
function foo($args) {
$args = 2;
return $args;
}
$a = 1;
$b = foo($a); // 値渡し
foo($a)
と記述するこで、foo
関数に $a
を値渡ししています。
スカラー型、配列型、オブジェクト型の値渡しをしたときにPHP内部でどのように処理されているのか見ていきましょう。
第1章で説明した代入と同じ動きをしますので、すぐに理解できると思います。
スカラー型の値渡し
スカラー型(整数型)を値渡しすると、PHP内部ではどのように処理されるのか見ていきましょう。
function foo($args) {
$args = 2;
return $args;
}
$a = 1;
$b = foo($a); // 値渡し
echo $a; // 結果:1
echo $b; // 結果:2
最終的に $a
の値は 1
、 $b
の値は 2
です。
この結果からわかるように値渡しした $a
の値は変わりません。
関数の仮引数 $args
に値渡しするときに $args = $a
のようにしてると考えるとわかりやすいかと思います。
第1章でも説明しましたが、$args = $a
のようにすると値は複製されて別々の値になると説明しました。
$args
と $a
は別々の値なので、値渡しした $a
は変わらないということです。
1行ずつ詳しく見ていきましょう。
1行目(スカラー型の値渡し)
$a = 1;
1行目は $a
に 1
を代入しています。
2行目の値渡し(スカラー型の値渡し)
foo($a);
2行目の右オペランドの値渡しの部分です。
foo
関数に $a
を値渡ししています。
foo
関数の仮引数 $args
は値渡しされた $a
と同じ (int)1
を指し示します。
コピーオンライトのため、値渡しした $a
と同じ (int)1
を指し示していますが、変更があった時点で複製されて書き換わります。
$a
と $args
は別々の値です。
そのため、関数内の処理が終了しても値渡しした $a
に変化はありません。
図に function stack
とありますが、関数スタック(function stack
)のことをコールスタックとも呼びます。
コールスタックとは
コールスタック (Call Stack)は、プログラムに実行中にサブルーチンに関する情報を格納するスタックである。実行中のサブルーチンとは、呼び出されたが処理を完了していないサブルーチンを意味する。実行スタック (Execution Stack)、制御スタック (Control Stack)、関数スタック (Function Stack)などとも呼ばれる。
コールスタックの機能にはいくつかありますが、関数や引数に関する情報を格納したり、関数の処理を終えた後、処理を呼び出し元に戻る場所を記憶するのに利用されます。
図にある function stack
は関数の引数を管理するもので (int)1
を指し示します。
そのため、値渡しの時点では refcount
が 3
になります。
foo関数内の代入(スカラー型の値渡し)
$args = 2;
foo
関数内では $args
に 2
を代入しています。
$args
は (int)2
の値を指し示すようになります。
それにあわせて (int)1
の refcount
は 2
になります。
foo関数の返り値と代入(スカラー型の値渡し)
return $args;
$b = foo($a);
foo
関数は $args
の値を返して終了します。
$args
の値は (int)2
です。
$b
は (int)2
を指し示すようになります。
foo
関数が終了すると function stack
と function symbol table
はなくなりますので、それにあわせて refcount
の数も変わります。
スカラー型の値渡しのポイント
- 関数やメソッドの引数に値を渡すことを値渡しと呼ぶ
- スカラー型を値渡しすると値は複製されるため、値渡しした基の値は変わらない
- 関数実行中は
function stack
とfunction symbol table
からも値を指し示すため、refcount
も変わる
配列型の値渡し
配列型を値渡しすると、PHP内部ではどのように処理されるのか見ていきましょう。
function foo($args) {
$args[] = 4;
}
function bar($args) {
$args[] = 5;
return $args;
}
$a = [1,2,3];
foo($a);
var_dump($a);
$b = bar($a);
foo
関数と bar
関数を定義しています。
foo
関数は配列に 4
を代入していますが、return
の返り値がありません。
bar
関数は配列に 5
を代入した後に return
で値を返しています。
それぞれの関数を実行したあとで値がどのように変わるのか、1行ずつ見ていきましょう。
1行目(配列型の値渡し)
$a = [1,2,3];
$a
に配列を代入しています。
各配列のキーは各値を指し示しています。
2行目の値渡し(配列型の値渡し)
foo($a);
先ほども説明しましたが、foo
関数に返り値はありません。
そのため、返り値を変数に代入することもしていません。
この行は foo
関数を実行しているだけです。
foo
関数に $a
を値渡ししています。
foo
関数の仮引数 $args
は $a
と同じ配列を指し示します。
配列型の代入のときにも説明しましたが、コピーオンライトのため同じ配列を指し示していますが、配列の値に変更があった時点で配列が複製されます。
$a
と $args
は別々の配列です。
foo関数内の代入(配列型の値渡し)
$args[] = 4;
$args
の配列に 4
を代入しました。
この時点で配列の複製と書き換えが行われます。
$a
と $args
は別々の配列を指し示すようになりました。
各値もコピーオンライトのため、同じ値は複製されず既存の値を指し示します。
値に変更があった時点で複製して書き換えが行われます。
この関数はこの処理で終了します。
関数の処理が終了すると function stack
と function table
はなくなります。
3行目(配列型の値渡し)
var_dump($a);
先ほど foo
関数が終了した後の $a
です。
関数を実行する前の $a
と同じです。
配列型を値渡しすると配列は複製されるため、値渡しした $a
には変化がないことがわかります。
4行目の値渡し(配列型の値渡し)
bar($a)
bar
関数に $a
を値渡ししています。
bar
関数の仮引数 $args
は $a
と同じ配列を指し示します。
bar関数内の代入(配列型の値渡し)
$args[] = 5;
$args
の配列に 5
を代入しました。
この時点で配列の複製と書き換えが行なわれます。
変更がない値は $a
と同じ値を指し示していますが、どちらかの値に変更があった時点で複製して書き換えが行われます。
bar関数の返り値と代入(配列型の値渡し)
return $args;
$b = bar($a);
bar
関数の最後に $args
の値を return
で返しています。
その返り値を $b
に代入しています。
関数の実行が終了したので図の中には function stack
と function symbol table
はありませんが、$b
は返り値の $args
と同じ配列を指しています。
値渡しした時点で別の配列になるため、値渡しした $a
に変化はありません。
配列型の値渡しのポイント
- 配列型を値渡しすると配列は複製されるため、値渡しした基の配列は変わらない
オブジェクト型の値渡し
オブジェクト型を値渡しすると、PHP内部ではどのように処理されるのか見ていきましょう。
class foo {
public $bar = 1;
public $baz = "a";
}
function qux($args) {
$args->bar = 3;
$args = 4;
}
$a = new foo;
qux($a);
var_dump($a);
オブジェクト型の値渡しと言っても、オブジェクト型の代入と同じです。
おさらいになりますが、オブジェクト型の代入の場合は、スカラー型と配列型のときとは少し異なりました。
$a = $b
のようにオブジェクト型を代入したときは、最初同じオブジェクトを指し示し、オブジェクトの各プロパティの値が変わってもオブジェクト自体が複製されることはありませんでした。
変数名が直接指し示すものが変わるときに値の複製と書き換えが行われます。
この2点について覚えておいてください。
オブジェクト型の値渡しの場合でも同じようになります。
オブジェクト型の値渡しするとどのように変わるのか、1行ずつ見ていきましょう。
1行目(オブジェクト型の値渡し)
$a = new foo;
$a
に foo
クラスのオブジェクト(インスタンス)を代入しています。
$a
は (object)foo
を指し示しています。
2行目(オブジェクト型の値渡し)
qux($a);
qux
関数に $a
を値渡ししています。
qux
関数の仮引数 $args
は $a
と同じオブジェクトを指し示します。
qux関数内の代入(オブジェクト型の値渡し)
$args->bar = 3;
$args
のオブジェクトの bar
プロパティに 3
を代入しています。
bar
プロパティは (int)3
を指し示します。
先ほども説明しましたが、オブジェクトの各プロパティの値に変更があってもオブジェクト自体は複製されません。
qux関数内の代入(オブジェクト型の値渡し)
$args = 4;
$args
に 4
を代入しています。
この時点で値の複製と書き換えが行われ、$args
は (int)4
を指し示します。
それにあわせて (object)foo
の refcount
は 2
になります。
3行目(オブジェクト型の値渡し)
var_dump($a);
qux
関数が終了すると function stack
と function symbol table
はなくなります。
オブジェクト型を値渡しすると、最初は同じオブジェクトを指し示します。
そのときに各プロパティの値を変更してもオブジェクトが複製されることはないので、値渡しした $a
のオブジェクトにも影響します。
そのため、最終的に $a
の bar
プロパティの値が値渡しする前と変わっています。
オブジェクト型の値渡しのポイント
- オブジェクト型を値渡しすると、仮引数は最初同じオブジェクトを指し示し、そのオブジェクトの各プロパティの値を変更しても、オブジェクト自体は複製されない
- 仮引数の変数が直接指し示すものに変更があった時点で複製されて、書き換えが行われる
最後に
この記事では値の代入と値渡しについて見てきました。
PHP内部でどのように処理されるのか見ることで、値の代入と値渡しについて理解が深まったかと思います。
値の代入と値渡しの他に 参照の代入 と 参照渡し があります。
参照の代入については別の記事にまとめましたので、下記をご覧ください。
今回の記事で初心者の理解が少しでも深まれば幸いです。
私自身未熟ですので説明に誤りがあるかもしれません。
誤りがあれば、ご指摘ください((_ _ (´ω` )ペコ
参考サイト
この記事を書くにあたり、参考にしたサイトです。
ありがとうございます。
- Unlocking PHP Secrets Forum PHP 2007
- How PHP manages variables - entwickler.de
- Zoom on PHP objects and classes (PHP 5)
- 【PHP7内核剖析】3.4 面向对象-对象的实现
- A Close Look Into PHP Zval
- PHP内核探索之变量(2)-理解引用
note
note でも記事を公開してるので、興味がある方はご覧ください。