1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHPの配列の参照とコピーの挙動(WEB+DB PRESS Vol.103の「事業を支えるPHP」の値渡しに対する突っ込み)

Last updated at Posted at 2018-03-21

https://www.amazon.co.jp/WEB-DB-PRESS-Vol-103-%E8%A5%BF%E6%9D%91/dp/4774195545/ref=pd_sim_14_2?_encoding=UTF8&psc=1&refRID=BTDQPWNWFY136VE48HCK
「WEB+DB PRESS Vol.103」の連載、「事業を支えるPHP」を読んでの突っ込みです。

※記事の内容は全体的に素晴らしいと思います。筆者と記事を貶めるような意図はありません。誤解が広まってしまうのは良くないな〜&自分の知識を確認するのが目的です。

指摘したい箇所

「配列の代入や関数へ引数で渡すときには参照は渡されず、すべて配列がコピーされる挙動になります。」

$array1 = ['hoge' => 'fuga'];
$array2 = $array1; // 配列がコピーされる
$array2['hoge'] = 'piyo'; // 変更は$array2に対してのみ行われる

指摘内容

問題としているのは、「コピー」についてです。
PHPにおいては、コピーオンライトという仕組みがあって、値が変更されるタイミングで初めて値がコピーされる、という仕組みがあります。

これを理解しないと、以下のような誤解が生まれがちです。

// 巨大な配列
$array1 = [];
for ($i = 0; $i < 1000000; $i++) {
    $array1[$i] = $i;
}

$array2 = $array1; // 巨大な配列がコピーされるのでメモリをたくさん食う

実際に計測してみます。
PHPのバージョンは7.1.11です。

<?php
echo "first:".memory_get_usage() / (1024 * 1024)."MB\n";

// 巨大な配列
$array1 = [];
for ($i = 0; $i < 1000000; $i++) {
    $array1[$i] = $i;
}
echo "second:".memory_get_usage() / (1024 * 1024)."MB\n";

$array2 = $array1; // 巨大な配列がコピーされるのでメモリをたくさん食う?
echo "thrid:".memory_get_usage() / (1024 * 1024)."MB\n";

結果

first:0.34778594970703MB
second:32.35179901123MB
thrid:32.35179901123MB

値がコピーされてない事がわかります。

次に配列の値を変更してみます。

<?php
echo "first:".memory_get_usage() / (1024 * 1024)."MB\n";

// 巨大な配列
$array1 = [];
for ($i = 0; $i < 1000000; $i++) {
    $array1[$i] = $i;
}
echo "second:".memory_get_usage() / (1024 * 1024)."MB\n";

$array2 = $array1; // 巨大な配列がコピーされるのでメモリをたくさん食う?
echo "thrid:".memory_get_usage() / (1024 * 1024)."MB\n";

$array1[0] = "test";
echo "fourth:".memory_get_usage() / (1024 * 1024)."MB\n";

結果

first:0.34822082519531MB
second:32.352233886719MB
thrid:32.352233886719MB
fourth:64.356216430664MB

値が変更されたタイミングで配列がコピーされている事がわかります。

指摘修正版

というわけで、以下が正しいと思います。

「配列の代入や関数へ引数で渡すときには参照は渡されず、値が渡されます。コピーオンライトという仕組みにより、値が変更されたタイミングで配列がコピーされる挙動になります。」

$array1 = ['hoge' => 'fuga'];
$array2 = $array1; // 値はまだコピーされない
$array2['hoge'] = 'piyo'; // 配列がコピーされ、変更は$array2に対してのみ行われる

所感等

マニュアルみても明示的に書いてないのと、他の言語ではあまり見られない特徴なので、誤解されがちだな〜と思います。
結論の、「参照を渡したくなるケースはほとんどないでしょう。プログラムを複雑にする原因にもなり得るので可能な限り避けると良いです。」には完全同意です。↓の記事にあるように、扱いを間違えるとパフォーマンス的にもデメリットが発生しますし、、、、
http://tanakahisateru.hatenablog.jp/entry/2013/12/12/012728

PHP5とPHP7

今回は配列の扱いなので関係ないですが、整数等の場合はPHP5とPHP7で挙動が違います。
PHP5までは全ての値について、今回検証した配列のような動き(コピーオンライト)をしますが、PHP7では普通に値がコピーされます。
https://www.slideshare.net/hnw/php7

参考

http://php.net/manual/ja/features.gc.refcounting-basics.php
http://keicode.com/cgi/object-in-php-5.php

2018/03/31追記

公式で捕捉いただけました
http://gihyo.jp/magazine/wdpress/archive/2018/vol103/support#supportInformation

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?