PHPの配列は、値として、または新しい変数への参照として、および関数に渡されるときにコピーされますか?


259

1)配列が引数としてメソッドまたは関数に渡される場合、それは参照渡しですか、それとも値渡しですか?

2)配列を変数に割り当てる場合、新しい変数は元の配列への参照ですか、それとも新しいコピーですか?
これを行うことについて:

$a = array(1,2,3);
$b = $a;

ある$bへの参照$a



3
@MarlonJerezIsla:関数内で配列を変更した場合にのみ配列が複製されるようです。まだ他の言語から来ているので、奇妙なようです。
user276648 2016年

回答:


276

あなたの質問の後半部分については、マニュアルの配列ページ状態は、(引用を)

配列の割り当てには常に値のコピーが含まれます。参照演算子を使用して、配列を参照でコピーします。

そして、与えられた例:

<?php
$arr1 = array(2, 3);
$arr2 = $arr1;
$arr2[] = 4; // $arr2 is changed,
             // $arr1 is still array(2, 3)

$arr3 = &$arr1;
$arr3[] = 4; // now $arr1 and $arr3 are the same
?>


最初の部分では、確かに最善の方法は;-)を試すことです

次のコード例を考えてみます。

function my_func($a) {
    $a[] = 30;
}

$arr = array(10, 20);
my_func($arr);
var_dump($arr);

それはこの出力を与えるでしょう:

array
  0 => int 10
  1 => int 20

これは、関数がパラメーターとして渡された「外部」配列を変更していないことを示します。これは参照ではなく、コピーとして渡されます。

参照渡しする場合は、次のように関数を変更する必要があります。

function my_func(& $a) {
    $a[] = 30;
}

そして出力は次のようになります:

array
  0 => int 10
  1 => int 20
  2 => int 30

今回は、配列が「参照により」渡されたためです。


マニュアルの参照説明のセクション を読むのをためらわないでください:それはあなたの質問のいくつかに答えるべきです;-)


$ a =&$ this-> aのようなものはどうですか?$ aは&this-> aへの参照になりましたか?
フランク

1
を使用している&ので、そうです。php.net/ manual
Pascal MARTIN

1
聖なる牛、これが私が抱えていた問題だとは信じられません...これがレッスンである場合は、常に
オフショニング

2
こんにちはパスカル、コスタ・コントスの答えはより正確に思われます。私は彼の発見を確認するために簡単な簡単なテストをしますgist.github.com/anonymous/aaf845ae354578b74906彼の発見にもコメントできますか?
Cheok Yan Cheng 2014

1
これは私が抱えていた問題でもあります。ネストされた配列については奇妙なものだと思っていましたが、実際にはPHPでの配列割り当ての動作方法だけでした。
ジェレミーリスト

120

最初の質問に関しては、呼び出しているメソッド/関数内で変更されない限り、配列は参照渡しされます。メソッド/関数内で配列を変更しようとすると、最初に配列のコピーが作成され、次にコピーのみが変更されます。これにより、配列が値で渡されているように見えますが、実際にはそうではありません。

たとえば、この最初のケースでは、参照によって$ my_arrayを受け入れるように関数を定義していない場合でも(パラメーター定義で&文字を使用して)、参照によって渡されます(つまり、メモリを浪費しません)不要なコピーを含む)。

function handle_array($my_array) {  

    // ... read from but do not modify $my_array
    print_r($my_array);

    // ... $my_array effectively passed by reference since no copy is made
}

ただし、配列を変更する場合は、最初にそのコピーが作成されます(これにより、より多くのメモリが使用されますが、元の配列は影響を受けません)。

function handle_array($my_array) {

    // ... modify $my_array
    $my_array[] = "New value";

    // ... $my_array effectively passed by value since requires local copy
}

参考までに-これは「遅延コピー」または「コピーオンライト」として知られています。


8
これは非常に興味深い情報です。それは本当のように見えます。しかし、私はこの事実を裏付ける公式の文書を見つけることができませんでした。また、PHPのどのバージョンがこの遅延コピーの概念をサポートしているかを知る必要があります。誰かもっと情報がありますか?
Mario Awad 2013

8
更新、いくつかの公式ドキュメントを見つけたが、遅延コピーをサポートするPHPのバージョンを見つける必要がある(マニュアルでは「コピーオンライト」と呼んでいる):php.net/manual/en/internals2.variables.intro.php
Mario Awad

7
これは純粋にPHP仮想マシンの実装の決定であり、言語の一部ではありません。プログラマーには実際には見えません。コピーオンライトは確かにパフォーマンス上の理由から推奨されますが、すべての配列をコピーする実装はプログラマーの観点から違いがないため、言語のセマンティクスは値渡しを指定していると言えます。
Superfly

14
@Superflyは、メモリを使い果たすことなく、何百もの関数のスタックを介して100MBの配列を渡すことができるかどうかを知りたいときに、確かに違いをもたらします。それでもセマンティクスを値渡しと呼ぶのは正しいかもしれませんが、用語に関するそのような問題を除いて、ここで言及されている「実装の詳細」は、実際のP​​HPプログラマーにとって確かに重要です。
Mark Amery

3
これには別の癖があり、パフォーマンスについて考えるときに、コピーオンライトを認識することがさらに重要になります。参照渡しで配列を渡すと、値渡しよりもメモリを節約できると思うかもしれませんが(コピーオンライトについて知らなかった場合)、実際には逆の効果があります!配列がされている場合は、その後(ご自身またはサードパーティのコードで)値によって渡され、PHPは、持っていない完全なコピーを作成するか、それはもはや参照カウントを追跡することができます!もっとここに:stackoverflow.com/questions/21974581/...
ダン・キング

80

TL; DR

a)メソッド/関数は配列引数のみを読み取る => 暗黙の(内部)参照
b)メソッド/関数配列引数を変更する =>
c)メソッド/関数の配列引数は参照として明示的にマークされている(アンパサンドを使用) => 明示的な(ユーザーランド)参照

またはこれ:
- 非アンパサンド配列パラメーター:参照渡し; 書き込み操作は、配列の新しいコピー、最初の書き込みで作成されるコピーを変更します。
- アンパサンド配列param:参照渡し。書き込み操作は元の配列を変更します。

覚えておいてください- アンパサンド以外の配列パラメーターに書き込ん瞬間に、PHPは値をコピーします。それがcopy-on-write意味することです。この振る舞いのCのソースを紹介したいのですが、怖いです。xdebug_debug_zval()を使用することをお勧めします。

パスカル・マーティンは正しかった。コスタ・コントスはさらにそうでした。

回答

場合によります。

ロングバージョン

私はこれを自分のために書き留めていると思います。私はブログか何かを持っている必要があります...

人々が参照(または、その問題についてはポインタ)について話すときはいつでも、彼らは通常、ログマチーに終わります(このスレッドを見てください!)。
PHPは由緒ある言語であり、私は混乱に足を踏み入れるべきだと思いました(これは上記の回答の要約ですが)。なぜなら、2人が同時に正しい可能性があるとしても、彼らの頭を1つにまとめて1つの答えにした方がよいからです。

まず、白黒で答えない場合は、自分がペダントではないことを知っておく必要があります。物事は「はい/いいえ」よりも複雑です。

ご覧のとおり、値渡し/参照渡しの全体は、メソッド/関数スコープでその配列を正確に何に使用しているかに関係しています。

PHPは何と言っていますか?(別名「変更」)

マニュアルでは、この(強調鉱山)言います:

デフォルトでは、関数の引数をされている値渡し(関数内の引数の値がされた場合にそのことを変え、それは関数の外を変更しません)。関数が引数を変更できるようにするには、それらを参照渡しする必要があります。

関数への引数が常に参照渡しされるようにするには、関数定義の引数名にアンパサンド(&)を付加します

私の知る限り、大きくて真面目で神に正直なプログラマーがリファレンスについて話すとき、彼らは通常そのリファレンスの値を変更することについて話します。そしてそれはまさにマニュアルが述べていることです:hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value"

しかし、彼らが言及していない別のケースがあります:私が何も変更しない場合はどうでしょう-ただ読んでください?
明示的に参照をマークしないメソッドに配列を渡し、関数スコープでその配列を変更しない場合はどうなりますか?例えば:

<?php
function readAndDoStuffWithAnArray($array) 
{
    return $array[0] + $array[1] + $array[2];
}

$x = array(1, 2, 3);

echo readAndDoStuffWithAnArray($x);

私の仲間の旅行者、読んでください。

PHPは実際に何をしますか?(別名「メモリー・ワイズ」)

同じ大きくて深刻なプログラマーは、さらに深刻になると、参照に関する「メモリの最適化」について話します。PHPも同様です。なぜならPHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting、それが理由です。

巨大な配列をさまざまな関数に渡し、PHPがそれらのコピーを作成することは理想的ではありません(結局のところ、これは「値渡し」が行うことです)。

<?php

// filling an array with 10000 elements of int 1
// let's say it grabs 3 mb from your RAM
$x = array_fill(0, 10000, 1); 

// pass by value, right? RIGHT?
function readArray($arr) { // <-- a new symbol (variable) gets created here
    echo count($arr); // let's just read the array
}

readArray($x);

さて、これが実際に値渡しの場合、その配列のコピーが2つあるので、3MB以上のRAMがなくなってしまいますよね?

違う。限り、我々は変更しないよう$arrリファレンスだ変数を、メモリワイズ。見えないだけです。そのため、PHP はについて 話すときにユーザーランドの参照を言及し&$someVar内部と明示(アンパサンドを使用)を区別しています。

事実

そう、 when an array is passed as an argument to a method or function is it passed by reference?

私は3つの(ええ、3つの)ケースを思いつきました:
a)メソッド/関数は配列引数を読み取るだけです
b)メソッド/関数配列引数を変更します
c)メソッド/関数配列引数は参照として明示的にマークされています(アンパサンド)


まず、配列が実際に消費するメモリ量を見てみましょう(ここで実行):

<?php
$start_memory = memory_get_usage();
$x = array_fill(0, 10000, 1);
echo memory_get_usage() - $start_memory; // 1331840

その多くのバイト。すごい。

a)メソッド/関数が配列引数のみを読み取る

次に、前述の配列を引数として読み取るだけの関数を作成してみましょう。読み取りロジックにどれだけのメモリが必要かを確認します。

<?php

function printUsedMemory($arr) 
{
    $start_memory = memory_get_usage();

    count($arr);       // read
    $x = $arr[0];      // read (+ minor assignment)
    $arr[0] - $arr[1]; // read

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

$x = array_fill(0, 10000, 1); // this is 1331840 bytes
printUsedMemory($x);

推測したいですか?80点!自分の目で確かめてください。これは、PHPマニュアルで省略されている部分です。場合$arrparamは、実際に渡された値によるました、あなたは次のように何かを参照してくださいね1331840バイト。$arr参照のように振る舞うようですね。それ参照であるためです-内部のものです。

b)メソッド/関数が配列引数を変更する

今、それから読み取るのではなく、そのパラメーターに書き込みましょう:

<?php

function printUsedMemory($arr)
{
    $start_memory = memory_get_usage();

    $arr[0] = 1; // WRITE!

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

$x = array_fill(0, 10000, 1);
printUsedMemory($x);

繰り返しますが、私にとっては、1331840にかなり近いです。この場合、配列実際ににコピーされてい$arrます。

c)メソッド/関数の配列引数が参照として明示的にマークされている(アンパサンドを使用)

次に、明示的な参照への書き込み操作に必要なメモリ量を見てみましょう(ここで実行)-関数のシグネチャのアンパサンドに注意してください。

<?php

function printUsedMemory(&$arr) // <----- explicit, user-land, pass-by-reference
{
    $start_memory = memory_get_usage();

    $arr[0] = 1; // WRITE!

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

$x = array_fill(0, 10000, 1);
printUsedMemory($x);

私の賭けはあなたが最大200を得るということです!したがって、これはアンパサンド以外のparamから読み取るのとほぼ同じ量のメモリを消費します。


メモリリークのデバッグで数時間節約できました!
Ragen Dazs 2017

2
Kosta Kontos:これは非常に重要な質問なので、これを承認済みの回答としてマークする必要があります。つまり、@ nevvermind:素晴らしいエッセイですが、トップのTL; DRセクションを含めてください。
AVIDeveloper 2017年

1
@nevvermind:私は頭字語グロッピーではありません。主な違いは、結論は通常記事の最後に表示されることですが、TL; DRは、長い分析を行う代わりに簡単に答える必要がある人のための最初の行として表示されます。あなたの研究は素晴らしいです、そしてこれは批判ではありません、ちょうど私の00.02ドルです。
AVIDeveloper 2017年

1
あなたが正しい。私は結論を上に置きました。しかし、結論に達する前に、人々がすべてを読むのに怠惰になるのをやめてほしいです。スクロールは簡単なので、物事の順序を変更する必要はありません。
nevvermind 2017年

1
コードパッドの例では数値がはるかに低いため、PHPは数年後にはより効率的になったと思います:)
drzaus

14

デフォルトでは

  1. プリミティブは値で渡されます。Javaとは異なり、PHPでは文字列はプリミティブです
  2. プリミティブの配列は値で渡されます
  3. オブジェクトは参照渡しされます
  4. オブジェクトの配列は値(配列)によって渡されますが、各オブジェクトは参照によって渡されます。

    <?php
    $obj=new stdClass();
    $obj->field='world';
    
    $original=array($obj);
    
    
    function example($hello) {
        $hello[0]->field='mundo'; // change will be applied in $original
        $hello[1]=new stdClass(); // change will not be applied in $original
        $
    }
    
    example($original);
    
    var_dump($original);
    // array(1) { [0]=> object(stdClass)#1 (1) { ["field"]=> string(5) "mundo" } } 

注:最適化として、関数内で変更されるまで、すべての値が参照として渡されます。それが変更され、値が参照によって渡された場合、それはコピーされ、コピーが変更されます。


4
この回答は先頭に+1する必要があります。「4-オブジェクトの配列は値(配列)によって渡されますが、各オブジェクトは参照によって渡されます。」そのせいで頭を掻いてた!
16年

@magallanes greatも私にとって最初に評価されるべきです、あなたは私が持っていたオブジェクト配列の問題を明らかにします。2つの配列変数(元の変数とコピー)の1つだけで配列内のオブジェクトを変更する方法はありますか?
fede72bari

5

配列がPHPのメソッドまたは関数に渡される場合、次のように明示的に参照渡ししない限り、値によって渡されます。

function test(&$array) {
    $array['new'] = 'hey';
}

$a = $array(1,2,3);
// prints [0=>1,1=>2,2=>3]
var_dump($a);
test($a);
// prints [0=>1,1=>2,2=>3,'new'=>'hey']
var_dump($a);

2番目の質問で$bは、はへの参照で$aはなく、のコピーです$a

最初の例と同様に、次のようにして参照でき$aます。

$a = array(1,2,3);
$b = &$a;
// prints [0=>1,1=>2,2=>3]
var_dump($b);
$b['new'] = 'hey';
// prints [0=>1,1=>2,2=>3,'new'=>'hey']
var_dump($a);

1

このスレッドは少し古いですが、ここで私が遭遇したばかりの何か:

このコードを試してください:

$date = new DateTime();
$arr = ['date' => $date];

echo $date->format('Ymd') . '<br>';
mytest($arr);
echo $date->format('Ymd') . '<br>';

function mytest($params = []) {
    if (isset($params['date'])) {
        $params['date']->add(new DateInterval('P1D'));
    }
}

http://codepad.viper-7.com/gwPYMw

$ paramsパラメータにアンプはありませんが、それでも$ arr ['date']の値が変更されることに注意してください。これは、他のすべての説明とここまで考えたことと実際には一致しません。

$ params ['date']オブジェクトのクローンを作成した場合、2番目に出力された日付は同じままです。文字列に設定しただけでは、出力にも影響しません。


3
配列はコピーされますが、ディープコピーではありません。つまり、数値や文字列などのプリミティブ値は$ paramにコピーされますが、オブジェクトの場合は、複製されるオブジェクトの代わりに参照がコピーされます。$ arrは$ dateへの参照を保持しており、コピーされた配列$ paramsも保持しています。したがって、値を変更する関数を$ params ['date']で呼び出すと、$ arr ['date']と$ dateも変更されます。$ params ['date']を文字列に設定すると、$ paramsの$ dateへの参照を別のものに置き換えるだけです。
ejegg

1

回答の1つを拡張するために、参照によって明示的に渡されない限り、多次元配列のサブ配列も値によって渡されます。

<?php
$foo = array( array(1,2,3), 22, 33);

function hello($fooarg) {
  $fooarg[0][0] = 99;
}

function world(&$fooarg) {
  $fooarg[0][0] = 66;
}

hello($foo);
var_dump($foo); // (original array not modified) array passed-by-value

world($foo);
var_dump($foo); // (original array modified) array passed-by-reference

結果は次のとおりです。

array(3) {
  [0]=>
  array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
  [1]=>
  int(22)
  [2]=>
  int(33)
}
array(3) {
  [0]=>
  array(3) {
    [0]=>
    int(66)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
  [1]=>
  int(22)
  [2]=>
  int(33)
}

0

PHPでは、次のスニペットに示すように、明示的に参照渡ししない限り、配列はデフォルトで関数に渡されます。

$foo = array(11, 22, 33);

function hello($fooarg) {
  $fooarg[0] = 99;
}

function world(&$fooarg) {
  $fooarg[0] = 66;
}

hello($foo);
var_dump($foo); // (original array not modified) array passed-by-value

world($foo);
var_dump($foo); // (original array modified) array passed-by-reference

出力は次のとおりです。

array(3) {
  [0]=>
  int(11)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
array(3) {
  [0]=>
  int(66)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.