As matrizes no PHP são copiadas como valor ou como referência a novas variáveis ​​e quando passadas para funções?

259

1) Quando uma matriz é passada como argumento para um método ou função, é passada por referência ou por valor?

2) Ao atribuir uma matriz a uma variável, a nova variável é uma referência à matriz original ou é uma nova cópia?
Que tal fazer isso:

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

É $buma referência a $a?

Frank
fonte
Veja também Quando-faz-foreach-copy
Nawfal
3
@MarlonJerezIsla: parece que o Array só é clonado se você o modificar dentro da função. Ainda vindo de outras línguas, parece estranho.
user276648

Respostas:

276

Para a segunda parte da sua pergunta, consulte a página da matriz do manual , que declara (citando) :

A atribuição de matrizes sempre envolve cópia de valor. Use o operador de referência para copiar uma matriz por referência.

E o exemplo dado:

<?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
?>


Para a primeira parte, a melhor maneira de ter certeza é tentar ;-)

Considere este exemplo de código:

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

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

Isso dará a saída:

array
  0 => int 10
  1 => int 20

O que indica que a função não modificou a matriz "externa" que foi passada como parâmetro: é passada como uma cópia e não como uma referência.

Se você deseja que seja passado por referência, terá que modificar a função, desta maneira:

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

E a saída se tornará:

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

Como, desta vez, a matriz foi passada "por referência".


Não hesite em ler a seção Referências explicadas do manual: ele deve responder a algumas de suas perguntas ;-)

Pascal MARTIN
fonte
que tal algo como $ a = & $ this-> a. Agora $ a é uma referência a & this-> a?
Frank
1
Como você está usando o &, sim, ele deveria - veja php.net/manual/en/…
Pascal MARTIN
1
vaca sagrada, eu não posso acreditar que este é o problema que eu tinha ... isso deve ser uma lição, sempre ler o manual do offing
Heavy_Bullets
2
Olá Pascal, achei a resposta do Kosta Kontos mais precisa. Faço um teste rápido simples para confirmar sua descoberta gist.github.com/anonymous/aaf845ae354578b74906 Você também pode comentar sobre essa descoberta?
Cheok Yan Cheng
1
Este é o problema que eu também estava tendo: pensei que era algo estranho em matrizes aninhadas, mas na verdade era exatamente como a atribuição de matrizes funciona em PHP.
amigos estão
120

Com relação à sua primeira pergunta, o array é passado por referência, a menos que seja modificado no método / função que você está chamando. Se você tentar modificar a matriz dentro do método / função, uma cópia dela será feita primeiro e somente a cópia será modificada. Isso faz parecer que a matriz é passada por valor quando na verdade não é.

Por exemplo, neste primeiro caso, mesmo que você não esteja definindo sua função para aceitar $ my_array por referência (usando o caractere & na definição de parâmetro), ela ainda será passada por referência (por exemplo: você não desperdiça memória com uma cópia desnecessária).

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
}

No entanto, se você modificar a matriz, uma cópia dela será feita primeiro (que usa mais memória, mas deixa a matriz original inalterada).

function handle_array($my_array) {

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

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

Para sua informação, isso é conhecido como "cópia lenta" ou "cópia na gravação".

Kosta Kontos
fonte
8
Esta é uma informação super interessante! Parece que é verdade; mas não consegui encontrar nenhuma documentação oficial que apoiasse esse fato. Também precisamos saber quais versões do PHP suportam esse conceito de cópia lenta. Alguém tem mais informações?
Mario Awad
8
O Update, encontrou alguma documentação oficial, ainda precisa descobrir qual versão do PHP suporta cópia lenta (eles chamam de "copy on write" no manual): php.net/manual/en/internals2.variables.intro.php
Mario Awad
7
Isso é puramente uma decisão de implementação da máquina virtual PHP, e não faz parte da linguagem - na verdade, não é visível para o programador. A cópia na gravação é certamente recomendada por razões de desempenho, mas uma implementação que copia todas as matrizes não tem diferença da perspectiva do programador, portanto, podemos dizer que a semântica da linguagem especifica a passagem por valor.
Superfly 16/09
14
@Superfly certamente faz diferença quando quero saber se posso passar minha matriz de 100 MB por uma pilha de dezenas de funções sem ficar sem memória! Você pode estar certo de que, no entanto, é correto chamar a semântica de passada por valor, mas deixando de lado essas dúvidas sobre a terminologia, os "detalhes da implementação" mencionados aqui certamente importam para os programadores de PHP no mundo real.
Mark Amery
3
Há outra peculiaridade nisso, que torna o conhecimento da cópia na gravação ainda mais importante quando se pensa em desempenho. Você pode pensar que a passagem de matrizes por referência economiza memória em comparação com a passagem por valor (se você não soubesse sobre copiar na gravação), mas ela pode realmente ter o efeito oposto ! Se a matriz é posteriormente passados por valor (por conta própria ou 3 código partido), PHP, em seguida, tem de fazer uma cópia integral ou já não pode acompanhar a contagem de referência! Mais aqui: stackoverflow.com/questions/21974581/…
Dan King
80

TL; DR

a) o método / função lê apenas o argumento da matriz => referência implícita (interna)
b) o método / função modifica o argumento da matriz => valor
c) o argumento da matriz do método / função é explicitamente marcado como uma referência (com um e comercial) => referência explícita (terra do usuário)

Ou isto:
- parâmetro não comercial e comercial : passado por referência; as operações de gravação alteram uma nova cópia da matriz, cópia criada na primeira gravação;
- e comercial array param : passado por referência; as operações de gravação alteram a matriz original.

Lembre-se - o PHP faz uma cópia de valor no momento em que você escreve no parâmetro da matriz não comercial. É isso que copy-on-writesignifica. Eu adoraria mostrar a fonte C desse comportamento, mas é assustador lá. Melhor usar xdebug_debug_zval () .

Pascal MARTIN estava certo. Kosta Kontos foi ainda mais.

Responda

Depende.

Versão longa

Acho que estou escrevendo isso para mim. Eu deveria ter um blog ou algo assim ...

Sempre que as pessoas falam de referências (ou ponteiros, na verdade), elas geralmente acabam em um logotipo (apenas olhe para este tópico !).
Sendo o PHP uma linguagem venerável, pensei que deveria aumentar a confusão (mesmo que este seja um resumo das respostas acima). Porque, embora duas pessoas possam estar certas ao mesmo tempo, é melhor você apenas quebrar a cabeça em uma resposta.

Primeiro, você deve saber que não é pedante se não responder de maneira em preto e branco . As coisas são mais complicadas do que "sim / não".

Como você verá, todo o valor por referência / referência está muito relacionado ao que exatamente você está fazendo com essa matriz no seu escopo de método / função: lendo ou modificando?

O que o PHP diz? (também conhecido como "alteração")

O manual diz o seguinte (ênfase minha):

Por padrão, os argumentos da função são passados ​​por valor (para que, se o valor do argumento dentro da função for alterado , ele não seja alterado fora da função). Para permitir que uma função modifique seus argumentos, eles devem ser passados ​​por referência .

Para ter um argumento para uma função sempre passada por referência, coloque um e comercial (&) no nome do argumento na definição da função

Até onde eu sei, quando grandes programadores sérios e honestos falam sobre referências, geralmente falam em alterar o valor dessa referência . E isso é exatamente o que as negociações manuais sobre: hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Há outro caso que eles não mencionam: e se eu não mudar nada - apenas leia?
E se você passar uma matriz para um método que não marca explicitamente uma referência e não alteramos essa matriz no escopo da função? Por exemplo:

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

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

echo readAndDoStuffWithAnArray($x);

Continue lendo, meu companheiro de viagem.

O que o PHP realmente faz? (também conhecido como "memória")

Os mesmos programadores grandes e sérios, quando ficam ainda mais sérios, falam sobre "otimizações de memória" em relação às referências. O mesmo acontece com o PHP. Porque PHP is a dynamic, loosely typed language, that uses copy-on-write and reference countingé por isso .

Não seria ideal passar matrizes ENORME para várias funções, e o PHP para fazer cópias delas (afinal, o que "passa por valor" faz):

<?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);

Bem, agora, se isso realmente foi passado por valor, teríamos mais de 3 MB + RAM, porque existem duas cópias dessa matriz, certo?

Errado. Contanto que não alteremos a $arrvariável, isso é uma referência em memória . Você simplesmente não vê. É por isso que o PHP menciona referências de terra do usuário ao falar sobre &$someVar, para distinguir entre internas e explícitas (com e comercial).

Fatos

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

Eu vim com três (sim, três) casos:
a) o método / função lê apenas o argumento da matriz
b) o método / função modifica o argumento da matriz
c) o argumento da matriz do método / função é explicitamente marcado como uma referência (com um e comercial)


Primeiro, vamos ver quanta memória esse array realmente consome (execute aqui ):

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

Que muitos bytes. Ótimo.

a) o método / função lê apenas o argumento do array

Agora vamos criar uma função que apenas leia o referido array como argumento e veremos quanta memória a lógica de leitura leva:

<?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);

Quer adivinhar? Eu tenho 80! Veja você mesmo . Esta é a parte que o manual do PHP omite. Se o $arrparâmetro fosse realmente passado por valor, você veria algo semelhante aos 1331840bytes. Parece que $arrse comporta como uma referência, não é? Isso porque é uma referência - interna.

b) o método / função modifica o argumento do array

Agora, vamos escrever para esse parâmetro, em vez de ler:

<?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);

Mais uma vez, veja por si mesmo , mas, para mim, isso é muito próximo de 1331840. Portanto, nesse caso, a matriz está realmente sendo copiada $arr.

c) o argumento de matriz de método / função é explicitamente marcado como uma referência (com um e comercial)

Agora vamos ver quanta memória uma operação de gravação leva a uma referência explícita (execute aqui ) - observe oe comercial na assinatura da função:

<?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);

Minha aposta é que você receba 200 no máximo! Portanto, isso consome aproximadamente a mesma quantidade de memória que a leitura de um parâmetro que não seja E comercial .

nevvermind
fonte
Me salvou algumas horas na depuração de um vazamento de memória!
Ragen Dazs
2
Kosta Kontos: Essa é uma pergunta tão importante que você deve marcar como resposta aceita. Dito isto, @nevvermind: Ótimo ensaio, mas inclua uma seção de TL; DR superior.
AVIDeveloper 19/01/19
1
@nevvermind: Eu não sou um acrônimo para groopy, o principal diferencial é que as Conclusões geralmente aparecem no final de um artigo, enquanto TL; DR aparece como a primeira linha para aqueles que precisam apenas de uma resposta curta, em vez de passar por uma longa análise . Sua pesquisa é boa e isso não é crítica, apenas meus US $ 00,02.
AVIDeveloper
1
Você está certo. Eu coloquei as conclusões no topo. Mas eu ainda gostaria que as pessoas deixassem de ter preguiça de ler a coisa toda, antes de chegar a uma conclusão . A rolagem é muito fácil para nos incomodarmos em mudar a ordem das coisas.
nevvermind
1
Eu acho que PHP ficou anos mais eficientes mais tarde, porque seus exemplos do controlador remoto dar números muito mais baixos :)
drzaus
14

Por padrão

  1. Primitivas são passadas por valor. Improvável para Java, a string é primitiva no PHP
  2. Matrizes de primitivas são passadas por valor
  3. Objetos são passados ​​por referência
  4. Matrizes de objetos são passadas por valor (a matriz), mas cada objeto é passado por referência.

    <?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" } } 

Nota: Como uma otimização, todos os valores são passados ​​como referência até serem modificados dentro da função. Se ele foi modificado e o valor foi passado por referência, é copiado e a cópia é modificada.

Magallanes
fonte
4
Esta resposta deve ser marcada com +1 no topo. Ele contém uma mancha obscura que outras respostas não mencionam: "4 - Matrizes de objetos são passadas por valor (a matriz), mas cada objeto é passado por referência." Eu estava coçando a cabeça por causa disso!
21316 Augustin
@magallanes great também deve ser classificado em primeiro lugar para mim, você me esclarece um problema de matriz de objetos que eu tive. Existe alguma maneira de modificar um objeto em uma matriz apenas em uma das duas variáveis ​​da matriz (o original e a cópia)?
Fev72bari 13/09/19
5

Quando uma matriz é passada para um método ou função no PHP, é passada por valor, a menos que você a passe explicitamente por referência, da seguinte maneira:

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);

Na sua segunda pergunta, $bnão é uma referência a $a, mas uma cópia de $a.

Muito parecido com o primeiro exemplo, você pode $afazer referência fazendo o seguinte:

$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);
Corey Ballou
fonte
1

Esta discussão é um pouco mais antiga, mas aqui algo que me deparei:

Tente este código:

$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

Observe que não há amplificador para o parâmetro $ params e ainda assim altera o valor de $ arr ['date']. Isso realmente não combina com todas as outras explicações aqui e o que eu pensava até agora.

Se eu clonar o objeto $ params ['date'], a segunda data de saída permanecerá a mesma. Se eu apenas configurá-lo como uma string, também não afeta a saída.

robbash
fonte
3
A matriz é copiada, mas não é uma cópia profunda . Isso significa que valores primitivos como números e seqüências de caracteres são copiados para $ param, mas para objetos, a referência é copiada em vez de o objeto ser clonado. $ arr está mantendo uma referência a $ date, assim como a matriz copiada $ params. Então, quando você chama uma função em $ params ['date'] que altera seu valor, você também muda $ arr ['date'] e $ date. Quando você define $ params ['date'] como uma string, substitui a referência de $ params para $ date por outra coisa.
ejegg
1

Para estender uma das respostas, também as sub-matrizes de matrizes multidimensionais são passadas por valor, a menos que sejam explicitamente passadas por referência.

<?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

O resultado é:

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
fonte
0

Em matrizes PHP, são passadas para funções por valor, por padrão, a menos que você as passe explicitamente por referência, como mostra o seguinte trecho:

$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

Aqui está a saída:

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