Копируются ли массивы в PHP как значения или как ссылки на новые переменные, и когда передаются в функции?

259

1) Когда массив передается в качестве аргумента методу или функции, передается ли он по ссылке или по значению?

2) При назначении массива переменной, новая переменная является ссылкой на исходный массив, или это новая копия?
Как насчет этого:

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

Является ли $bссылка $a?

Фрэнк
источник
Также см.
Статью «
3
@MarlonJerezIsla: похоже, массив только клонируется, если вы измените его внутри функции. По-прежнему происходит с других языков, это кажется странным.
user276648

Ответы:

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/en/…
Паскаль МАРТИН
1
святая корова, я не могу поверить, что это проблема, которая у меня была ... если это будет урок, всегда читайте руководство по эксплуатации
Heavy_Bullets
2
Привет Паскаль, я нашел, что ответ Косты Контоса кажется более точным. Я делаю простой быстрый тест, чтобы подтвердить его обнаружение gist.github.com/anonymous/aaf845ae354578b74906 Не могли бы вы также прокомментировать его обнаружение?
Чеок Ян Ченг
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 поддерживают эту концепцию отложенного копирования. У кого-нибудь есть больше информации?
Марио Авад
8
Обновление, нашел некоторую официальную документацию, все еще нужно найти, какая версия PHP поддерживает отложенное копирование (в руководстве они называют это «копировать при записи»): php.net/manual/en/internals2.variables.intro.php
Марио Авад
7
Это чисто решение по реализации виртуальной машины PHP, а не часть языка - на самом деле он невидим для программиста. Копирование при записи, безусловно, рекомендуется из соображений производительности, но реализация, которая копирует каждый массив, не имеет различий с точки зрения программиста, поэтому мы можем сказать, что семантика языка определяет передачу по значению.
Superfly
14
@Superfly, безусловно, имеет значение, когда я хочу узнать, смогу ли я передать свой массив размером 100 МБ через стек десятков функций без исчерпания памяти! Вы можете быть правы, что, тем не менее, правильно называть семантику передачей по значению, но, оставляя в стороне такие споры по терминологии, упомянутые здесь «детали реализации», безусловно, имеют значение для программистов PHP в реальном мире.
Марк Амери
3
В этом есть еще одна причуда, которая делает понимание копирования при записи еще более важным, когда мы думаем о производительности. Можно подумать, что передача массивов по ссылке экономит память по сравнению с передачей по значению (если вы не знали о копировании при записи), но на самом деле это может иметь обратный эффект! Если впоследствии массив передается по значению (по собственному или стороннему коду), PHP затем должен сделать полную копию, или он больше не может отслеживать количество ссылок! Подробнее здесь: stackoverflow.com/questions/21974581/…
Дэн Кинг,
80

TL; DR

a) метод / функция читает только аргумент массива => неявная (внутренняя) ссылка
b) метод / функция изменяет аргумент массива => значение
c) аргумент массива метода / функции явно помечается как ссылка (с амперсандом) => явная (пользовательская) ссылка

Или это:
- параметр массива без амперсанда : передается по ссылке; операции записи изменяют новую копию массива, копия которой создается при первой записи;
- параметр массива амперсанд : передается по ссылке; Операции записи изменяют исходный массив.

Помните - PHP копирует значение в тот момент, когда вы записываете в параметр non-ampersand array. Вот что copy-on-writeзначит. Я хотел бы показать вам источник C этого поведения, но там страшно. Лучше использовать xdebug_debug_zval () .

Паскаль МАРТИН был прав. Коста Контос был еще больше.

Ответ

Это зависит.

Длинная версия

Я думаю, что записываю это для себя. У меня должен быть блог или кое-что ...

Всякий раз , когда люди говорят о ссылках (или указателей, по этому вопросу), они , как правило , в конечном итоге в словопрение (просто посмотрите на эту нить !).
PHP был почтенным языком, и я подумал, что должен допустить путаницу (даже если это резюме приведенных выше ответов). Потому что, хотя два человека могут быть правы одновременно, вам лучше просто разбить их головы в один ответ.

Во-первых, вы должны знать, что вы не педант, если не отвечаете черно-белым способом . Все сложнее, чем «да / нет».

Как вы увидите, вся вещь по значению / по ссылке очень сильно связана с тем, что именно вы делаете с этим массивом в своей области действия метода / функции: читая его или изменяя его?

Что говорит 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что вот почему .

Было бы не идеальным передавать массивы HUGE различным функциям, а 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);

Ну, а теперь, если бы это на самом деле было передачей по значению, у нас бы ушло около 3 Мб + ОЗУ, потому что есть две копии этого массива, верно?

Неправильно. Пока мы не меняем $arrпеременную, это ссылка для памяти . Вы просто не видите этого. Вот почему PHP упоминает пользователь землю ссылку , когда речь идет о &$someVar, чтобы различать внутренние и явные (с амперсандом) из них.

факты

Так, when an array is passed as an argument to a method or function is it passed by reference?

Я придумал три (да, три) случая:
а) метод / функция читает только аргумент массива
б) метод / функция модифицирует аргумент массива
в) аргумент массива метода / функции явно помечается как ссылка (с амперсанд)


Во-первых, давайте посмотрим, сколько памяти фактически использует этот массив (запустите здесь ):

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

Это много байтов. Отлично.

а) метод / функция читает только аргумент массива

Теперь давайте создадим функцию, которая только читает указанный массив в качестве аргумента, и посмотрим, сколько памяти занимает логика чтения:

<?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 опускает. Если бы $arrпараметр был фактически передан по значению, вы бы увидели нечто похожее на 1331840байты. Кажется, что $arrведет себя как ссылка, не так ли? Это потому , что это а ссылки - внутренний один.

б) метод / функция изменяет аргумент массива

Теперь давайте напишем в этот параметр вместо чтения из него:

<?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 макс! Так что это съедает примерно столько же памяти, сколько и чтение из параметра без амперсанда .

nevvermind
источник
Спасло меня пару часов в отладке утечки памяти!
Ragen Dazs
2
Коста Контос: Это такой важный вопрос, что вы должны пометить его как принятый ответ. Тем не менее, @nevvermind: отличное эссе, но, пожалуйста, включите верхний раздел TL; DR.
AVIDeveloper
1
@nevvermind: я не аббревиатура, главное отличие в том, что выводы обычно появляются в конце статьи, а TL; DR - в первой строке для тех, кому просто нужен короткий ответ, вместо того, чтобы долго анализировать , Ваше исследование хорошо, и это не критика, просто мои $ 00,02.
AVIDeveloper
1
Ты прав. Я сделал выводы наверху. Но я все же хотел бы, чтобы люди перестали лениться читать все это, прежде чем прийти к какому-либо заключению . Прокрутка слишком проста, чтобы мы могли изменить порядок вещей.
nevvermind
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 - Массивы объектов передаются по значению (массиву), но каждый объект передается по ссылке». Я почесал голову из-за этого!
Августин
@magallanes great должен быть оценен первым и для меня, вы разъясните мне проблему с массивом объектов, который у меня был. Есть ли способ изменить объект в массиве только в одной из двух переменных массива (оригинал и копия)?
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);

Во втором вопросе речь $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'], вторая выходная дата остается прежней. Если я просто установлю его в строку, это не повлияет на вывод.

robbash
источник
3
Массив копируется, но это не глубокая копия. Это означает, что примитивные значения, такие как числа и строки, копируются в $ param, но для объектов ссылка копируется вместо клонируемого объекта. $ arr содержит ссылку на $ date, как и скопированный массив $ params. Поэтому, когда вы вызываете функцию для $ params ['date'], которая изменяет ее значение, вы также меняете $ arr ['date'] и $ date. Когда вы устанавливаете $ params ['date'] в строку, вы просто заменяете ссылку $ params на $ date чем-то другим.
ejegg
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)
}
K.Karamazen
источник
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)
}
Джон Сондерсон
источник