¿Las matrices en PHP se copian como valor o como referencia a nuevas variables, y cuando se pasan a funciones?

259

1) Cuando una matriz se pasa como argumento a un método o función, ¿se pasa por referencia o por valor?

2) Al asignar una matriz a una variable, ¿la nueva variable es una referencia a la matriz original o es una copia nueva?
¿Qué hay de hacer esto?

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

¿Es $buna referencia a $a?

Franco
fuente
Ver también When-does-foreach-copy
nawfal
3
@MarlonJerezIsla: parece que la matriz solo se clona si la modifica dentro de la función. Todavía viene de otros idiomas, parece extraño.
usuario276648

Respuestas:

276

Para la segunda parte de su pregunta, consulte la página de matriz del manual , que establece (citando) :

La asignación de matriz siempre implica la copia de valores. Use el operador de referencia para copiar una matriz por referencia.

Y el ejemplo 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 la primera parte, la mejor manera de estar seguro es intentarlo ;-)

Considere este ejemplo de código:

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

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

Dará esta salida:

array
  0 => int 10
  1 => int 20

Lo que indica que la función no ha modificado la matriz "externa" que se pasó como parámetro: se pasa como una copia, y no como una referencia.

Si desea que se pase por referencia, deberá modificar la función de esta manera:

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

Y la salida se convertirá en:

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

Como, esta vez, la matriz se ha pasado "por referencia".


No dude en leer la sección de Referencias explicadas del manual: debe responder algunas de sus preguntas ;-)

Pascal MARTIN
fuente
¿Qué pasa con algo como $ a = & $ this-> a. ¿Es $ a ahora una referencia a & this-> a?
Frank
1
Como está utilizando &, sí, debería - ver php.net/manual/en/…
Pascal MARTIN
1
vaca sagrada, no puedo creer que este sea el problema que tuve ... si esto fuera una lección, siempre lea el manual de
operaciones
2
Hola Pascal, descubrí que la respuesta de Kosta Kontos parece ser más precisa. Hago una prueba rápida simple para confirmar su hallazgo gist.github.com/anonymous/aaf845ae354578b74906 ¿Puede comentar sobre su hallazgo también?
Cheok Yan Cheng
1
Este es el problema que yo también tenía: pensé que era algo extraño acerca de las matrices anidadas, pero en realidad era cómo funciona la asignación de matrices en PHP.
Jeremy List
120

Con respecto a su primera pregunta, la matriz se pasa por referencia A MENOS QUE se modifique dentro del método / función que está llamando. Si intenta modificar la matriz dentro del método / función, primero se hace una copia y luego solo se modifica la copia. Esto hace que parezca que la matriz se pasa por valor cuando en realidad no lo es.

Por ejemplo, en este primer caso, aunque no esté definiendo su función para aceptar $ my_array por referencia (al usar el carácter & en la definición del parámetro), todavía se pasa por referencia (es decir, no desperdicia memoria) con una copia innecesaria).

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
}

Sin embargo, si modifica la matriz, primero se realiza una copia (que usa más memoria pero no afecta a su matriz original).

function handle_array($my_array) {

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

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

FYI: esto se conoce como "copia diferida" o "copia en escritura".

Kosta Kontos
fuente
8
¡Esta es una información súper interesante! Parece que es verdad; pero no pude encontrar ninguna documentación oficial que respalde este hecho. También necesitamos saber qué versiones de PHP admiten este concepto de copia diferida. ¿Alguien tiene más información?
Mario Awad
8
La actualización, encontró algo de documentación oficial, todavía necesita encontrar qué versión de PHP admite la copia diferida (lo llaman "copia al escribir" en el manual): php.net/manual/en/internals2.variables.intro.php
Mario Awad
77
Esto es puramente una decisión de implementación de la máquina virtual PHP, y no es parte del lenguaje, en realidad no es visible para el programador. La copia en escritura ciertamente se recomienda por razones de rendimiento, pero una implementación que copia cada matriz no tiene ninguna diferencia desde la perspectiva del programador, por lo que podemos decir que la semántica del lenguaje especifica el paso por valor.
Superfly
14
@Superfly sin duda hace una diferencia cuando quiero saber si puedo pasar mi matriz de 100 MB a través de una pila de docenas de funciones sin quedarse sin memoria. Puede tener razón en que, no obstante, es correcto llamar a la semántica paso por valor, pero dejando de lado tales objeciones sobre la terminología, el "detalle de implementación" mencionado aquí ciertamente importa a los programadores de PHP en el mundo real.
Mark Amery el
3
Hay otra peculiaridad en esto, que hace que ser consciente de la copia en escritura sea aún más importante cuando se piensa en el rendimiento. Puede pensar que pasar matrices por referencia ahorra memoria en comparación con pasar por valor (si no sabía sobre la copia en escritura), ¡pero en realidad puede tener el efecto contrario ! Si la matriz es posteriormente pasa por valor (por su propia cuenta o tercera parte de código), PHP entonces tiene que hacer una copia completa o que ya no puede realizar un seguimiento de la cuenta de referencia! Más aquí: stackoverflow.com/questions/21974581/…
Dan King
80

TL; DR

a) el método / función solo lee el argumento de matriz => referencia implícita (interna)
b) el método / función modifica el argumento de matriz => valor
c) el argumento de matriz de método / función se marca explícitamente como una referencia (con un ampersand) => referencia explícita (usuario-tierra)

O esto:
- no-ampersand array param : pasado por referencia; las operaciones de escritura alteran una nueva copia de la matriz, copia que se crea en la primera escritura;
- ampersand array param : pasado por referencia; Las operaciones de escritura alteran la matriz original.

Recuerde: PHP realiza una copia de valor en el momento en que escribe en el parámetro de matriz no ampersand. Eso es lo que copy-on-writesignifica. Me encantaría mostrarle la fuente C de este comportamiento, pero da miedo allí. Mejor use xdebug_debug_zval () .

Pascal MARTIN tenía razón. Kosta Kontos lo fue aún más.

Responder

Depende.

Versión larga

Creo que estoy escribiendo esto por mí mismo. Debería tener un blog o algo así ...

Cada vez que la gente habla de referencias (o punteros, para el caso), generalmente terminan en una logomaquia (¡solo mira este hilo !).
Como PHP es un lenguaje venerable, pensé que debería aumentar la confusión (aunque este es un resumen de las respuestas anteriores). Porque, aunque dos personas pueden estar en lo cierto al mismo tiempo, es mejor que simplemente quiebren sus cabezas en una sola respuesta.

En primer lugar, debes saber que no eres un pedante si no contestas en blanco y negro . Las cosas son más complicadas que "sí / no".

Como verá, todo lo relacionado con el valor / por referencia está muy relacionado con lo que está haciendo exactamente con esa matriz en su alcance de método / función: ¿leerlo o modificarlo?

¿Qué dice PHP? (también conocido como "cambio sabio")

El manual dice esto (énfasis mío):

Por defecto, los argumentos de la función se pasan por valor (de modo que si se cambia el valor del argumento dentro de la función , no se cambia fuera de la función). Para permitir que una función modifique sus argumentos, se deben pasar por referencia .

Para tener un argumento para una función siempre pasado por referencia, anteponga un signo de y comercial (&) al nombre del argumento en la definición de la función

Por lo que puedo decir, cuando los programadores grandes, serios y honestos con Dios hablan de referencias, generalmente hablan de alterar el valor de esa referencia . Y eso es exactamente lo que las conversaciones manuales sobre: hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Sin embargo, hay otro caso que no mencionan: ¿qué pasa si no cambio nada, solo leo?
¿Qué sucede si pasa una matriz a un método que no marca explícitamente una referencia y no cambiamos esa matriz en el alcance de la función? P.ej:

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

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

echo readAndDoStuffWithAnArray($x);

Sigue leyendo, mi compañero de viaje.

¿Qué hace PHP realmente? (también conocido como "memoria sabia")

Los mismos programadores grandes y serios, cuando se vuelven aún más serios, hablan de "optimizaciones de memoria" con respecto a las referencias. También PHP. Porque PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting, por eso .

No sería ideal pasar matrices ENORMES a varias funciones y PHP para hacer copias de ellas (después de todo, eso es lo que hace "pasar por valor"):

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

Bueno, ahora, si esto fuera realmente de paso por valor, habríamos perdido 3mb + RAM, porque hay dos copias de esa matriz, ¿verdad?

Incorrecto. Mientras no cambiemos la $arrvariable, es una referencia, en cuanto a memoria . Simplemente no lo ves. Es por eso que PHP menciona referencias de usuarios cuando se habla de ellas &$someVar, para distinguir entre internas y explícitas (con ampersand).

Hechos

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

Se me ocurrieron tres (sí, tres) casos:
a) el método / función solo lee el argumento de matriz
b) el método / función modifica el argumento de matriz
c) el argumento de matriz de método / función está explícitamente marcado como referencia (con un ampersand)


En primer lugar, veamos cuánta memoria realmente consume esa matriz (ejecutar aquí ):

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

Que muchos bytes. Excelente.

a) el método / función solo lee el argumento de matriz

Ahora hagamos una función que solo lea dicha matriz como argumento y veremos cuánta memoria toma la lógica de lectura:

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

¿Quieres adivinar? ¡Tengo 80! Compruébalo por ti mismo . Esta es la parte que omite el manual de PHP. Si el $arrparámetro realmente se pasara por valor, vería algo similar a los 1331840bytes. Parece que se $arrcomporta como una referencia, ¿no? Eso es porque es una referencia interna.

b) el método / función modifica el argumento de matriz

Ahora, escribamos a ese parámetro, en lugar de leerlo:

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

Una vez más, ver por sí mismo , pero, para mí, que es bastante cerca de ser 1331840. Así que en este caso, la matriz está en realidad está copiando $arr.

c) el argumento de matriz de método / función se marca explícitamente como una referencia (con un ampersand)

Ahora veamos cuánta memoria toma una operación de escritura en una referencia explícita (ejecute aquí ) - observe el ampersand en la firma de la función:

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

¡Mi apuesta es que obtienes 200 máx. Por lo tanto, esto consume aproximadamente tanta memoria como la lectura de un no-ampersand param .

nevvermind
fuente
¡Me ahorró un par de horas para depurar una pérdida de memoria!
Ragen Dazs
2
Kosta Kontos: Esta es una pregunta tan importante que debe marcarla como la respuesta aceptada. Dicho esto, @nevvermind: Gran ensayo, pero incluya una sección superior de TL; DR.
AVIDeveloper
1
@nevvermind: no soy un acrónimo groopy, la diferencia principal es que las conclusiones aparecen generalmente al final de un artículo, mientras que TL; DR aparece como la primera línea para aquellos que solo necesitan una respuesta breve en lugar de pasar por un análisis extenso . Su investigación es buena y esto no es crítica, solo mis $ 00.02.
AVIDeveloper
1
Tienes razón. He puesto las conclusiones en la parte superior. Pero todavía me gustaría que la gente dejara de ser floja al leer todo el asunto, antes de llegar a una conclusión . Desplazarse es demasiado fácil para que nos molestemos en cambiar el orden de las cosas.
nevvermind
1
Supongo que PHP se ha vuelto más eficiente años más tarde porque sus ejemplos de teclado dan números mucho más bajos :)
drzaus
14

Por defecto

  1. Las primitivas se pasan por valor. Poco probable para Java, la cadena es primitiva en PHP
  2. Las matrices de primitivas se pasan por valor
  3. Los objetos se pasan por referencia
  4. Las matrices de objetos se pasan por valor (la matriz) pero cada objeto se pasa por referencia.

    <?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 optimización, cada valor individual se pasa como referencia hasta que se modifique dentro de la función. Si se modifica y el valor se pasó por referencia, se copia y se modifica la copia.

magallanes
fuente
44
Esta respuesta debe ser + 1 'al principio. Contiene un tema oscuro que otras respuestas no mencionan: "4 - Las matrices de objetos se pasan por valor (la matriz) pero cada objeto se pasa por referencia". ¡Me estaba rascando la cabeza por eso!
agosto
@magallanes genial también debería ser calificado primero para mí, me aclaras un problema de matriz de objetos que tenía. ¿Hay alguna forma de modificar un objeto en una matriz solo en una de las dos variables de matriz (la original y la copia)?
fede72bari
5

Cuando una matriz se pasa a un método o función en PHP, se pasa por valor a menos que lo pase explícitamente por referencia, así:

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

En su segunda pregunta, $bno es una referencia $a, sino una copia de $a.

Al igual que en el primer ejemplo, puede hacer referencia $ahaciendo lo siguiente:

$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
fuente
1

Este hilo es un poco más antiguo pero aquí algo que acabo de encontrar:

Prueba 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

Tenga en cuenta que no hay amplificador para el parámetro $ params y aún así cambia el valor de $ arr ['date']. Esto realmente no coincide con todas las otras explicaciones aquí y lo que pensé hasta ahora.

Si clono el objeto $ params ['date'], la segunda fecha de salida permanece igual. Si solo lo configuro en una cadena, tampoco afecta la salida.

robbash
fuente
3
La matriz se copia, pero no es una copia profunda . Esto significa que los valores primitivos como números y cadenas se copian en $ param, pero para los objetos, la referencia se copia en lugar del objeto que se está clonando. $ arr contiene una referencia a $ date, y también la matriz copiada $ params. Entonces, cuando llamas a una función en $ params ['date'] que altera su valor, también estás cambiando $ arr ['date'] y $ date. Cuando establece $ params ['date'] en una cadena, simplemente reemplaza la referencia de $ params a $ date con otra cosa.
Ejegg
1

Para ampliar una de las respuestas, también se pasan por valor las submatrices de matrices multidimensionales a menos que se pasen explícitamente por referencia.

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

El resultado es:

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

En PHP, las matrices se pasan a las funciones de forma predeterminada, a menos que las pase explícitamente como referencia, como muestra el siguiente fragmento:

$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

Aquí está la salida:

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