Les tableaux en PHP sont-ils copiés comme valeur ou comme référence à de nouvelles variables, et lorsqu'ils sont passés à des fonctions?

259

1) Lorsqu'un tableau est passé en argument à une méthode ou une fonction, est-il passé par référence ou par valeur?

2) Lors de l'affectation d'un tableau à une variable, la nouvelle variable est-elle une référence au tableau d'origine ou s'agit-il d'une nouvelle copie?
Et si vous faisiez ça:

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

Est $bune référence à $a?

Franc
la source
Voir aussi When-does-foreach-copy
nawfal
3
@MarlonJerezIsla: on dirait que le tableau n'est cloné que si vous le modifiez à l'intérieur de la fonction. Venant toujours d'autres langues, cela semble bizarre.
user276648

Réponses:

276

Pour la deuxième partie de votre question, consultez la page du tableau du manuel , qui indique (citation) :

L'affectation de tableau implique toujours une copie de valeur. Utilisez l'opérateur de référence pour copier un tableau par référence.

Et l'exemple donné:

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


Pour la première partie, le meilleur moyen d'en être sûr est d'essayer ;-)

Considérez cet exemple de code:

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

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

Cela donnera cette sortie:

array
  0 => int 10
  1 => int 20

Ce qui indique que la fonction n'a pas modifié le tableau "extérieur" qui a été passé en paramètre: il est passé en copie et non en référence.

Si vous voulez qu'il soit passé par référence, vous devrez modifier la fonction de cette façon:

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

Et la sortie deviendra:

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

Comme, cette fois, le tableau a été passé "par référence".


N'hésitez pas à lire la section Références expliquées du manuel: il devrait répondre à certaines de vos questions ;-)

Pascal MARTIN
la source
et quelque chose comme $ a = & $ this-> a. $ A est-il maintenant une référence à & this-> a?
Frank
1
Comme vous utilisez le &, oui, cela devrait - voir php.net/manual/en/…
Pascal MARTIN
1
sainte vache, je ne peux pas croire que c'est le problème que j'ai eu ... si cela devait être une leçon, lisez toujours le manuel de départ
Heavy_Bullets
2
Salut Pascal, j'ai trouvé que la réponse de Kosta Kontos semble être plus précise. Je fais un simple test rapide pour confirmer sa conclusion gist.github.com/anonymous/aaf845ae354578b74906 Pouvez-vous également commenter sa conclusion?
Cheok Yan Cheng
1
C'est le problème que j'avais aussi: je pensais que c'était quelque chose de bizarre à propos des tableaux imbriqués, mais c'était en fait juste la façon dont l'affectation des tableaux fonctionnait en PHP.
Jeremy List
120

En ce qui concerne votre première question, le tableau est passé par référence SAUF s'il est modifié dans la méthode / fonction que vous appelez. Si vous essayez de modifier le tableau dans la méthode / fonction, une copie de celui-ci est effectuée en premier, puis seule la copie est modifiée. Cela donne l'impression que le tableau est passé par valeur alors qu'en réalité il ne l'est pas.

Par exemple, dans ce premier cas, même si vous ne définissez pas votre fonction pour accepter $ my_array par référence (en utilisant le caractère & dans la définition du paramètre), elle est toujours passée par référence (c'est-à-dire: vous ne perdez pas de mémoire avec une copie inutile).

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
}

Cependant, si vous modifiez le tableau, une copie est effectuée en premier (qui utilise plus de mémoire mais ne modifie pas votre tableau d'origine).

function handle_array($my_array) {

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

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

FYI - ceci est connu comme "copie paresseuse" ou "copie sur écriture".

Kosta Kontos
la source
8
Ceci est une information super intéressante! On dirait que c'est vrai; mais je n'ai pu trouver aucune documentation officielle à l'appui de ce fait. Nous devons également savoir quelles versions de PHP prennent en charge ce concept de copie paresseuse. Quelqu'un a plus d'informations?
Mario Awad du
8
Mise à jour, trouvé de la documentation officielle, doit encore trouver quelle version de PHP prend en charge la copie paresseuse (ils l'appellent "copie sur écriture" dans le manuel): php.net/manual/en/internals2.variables.intro.php
Mario Awad
7
C'est purement une décision d'implémentation de la machine virtuelle PHP, et ne fait pas partie du langage - elle n'est pas réellement visible pour le programmeur. La copie sur écriture est certainement recommandée pour des raisons de performances, mais une implémentation qui copie chaque tableau n'a aucune différence du point de vue du programmeur, nous pouvons donc dire que la sémantique du langage spécifie la valeur de passage.
Superfly
14
@Superfly, cela fait certainement une différence lorsque je veux savoir si je peux passer mon tableau de 100 Mo à travers une pile de dizaines de fonctions sans manquer de mémoire! Vous avez peut-être raison de dire qu'il est tout de même juste d'appeler la sémantique pass-by-value, mais en laissant de côté ces critiques sur la terminologie, les "détails d'implémentation" mentionnés ici importent certainement aux programmeurs PHP dans le monde réel.
Mark Amery
3
Il y a une autre bizarrerie à cela, ce qui rend la connaissance de la copie sur écriture encore plus importante lorsque l'on pense aux performances. Vous pourriez penser que le passage de tableaux par référence économise de la mémoire par rapport au passage par valeur (si vous ne connaissiez pas la copie sur écriture), mais cela peut avoir l' effet inverse ! Si le tableau est ensuite passé par valeur (par votre propre code ou par un code tiers), PHP doit alors faire une copie complète ou il ne peut plus suivre le nombre de références! Plus ici: stackoverflow.com/questions/21974581/…
Dan King
80

TL; DR

a) la méthode / fonction lit uniquement l'argument tableau => référence implicite (interne)
b) la méthode / fonction modifie l'argument tableau => valeur
c) l'argument méthode / fonction tableau est explicitement marqué comme référence (avec une esperluette) => référence explicite (user-land)

Ou ceci:
- paramètre de tableau non-esperluette : passé par référence; les opérations d'écriture modifient une nouvelle copie du tableau, copie qui est créée lors de la première écriture;
- paramètre de tableau esperluette : transmis par référence; les opérations d'écriture modifient le tableau d'origine.

N'oubliez pas - PHP effectue une copie de valeur au moment où vous écrivez dans le paramètre du tableau non esperluette. Voilà ce que ça copy-on-writeveut dire. J'adorerais vous montrer la source C de ce comportement, mais c'est effrayant là-dedans. Mieux vaut utiliser xdebug_debug_zval () .

Pascal MARTIN avait raison. Kosta Kontos l'était encore plus.

Répondre

Ça dépend.

Version longue

Je pense que je l'écris pour moi. Je devrais avoir un blog ou quelque chose ...

Chaque fois que les gens parlent de références (ou de pointeurs, d'ailleurs), ils se retrouvent généralement dans une logomachie (il suffit de regarder ce fil !).
PHP étant un langage vénérable, j'ai pensé que je devrais ajouter à la confusion (même si c'est un résumé des réponses ci-dessus). Parce que, même si deux personnes peuvent avoir raison en même temps, il vaut mieux se casser la tête en une seule réponse.

Tout d'abord, vous devez savoir que vous n'êtes pas un pédant si vous ne répondez pas en noir et blanc . Les choses sont plus compliquées que «oui / non».

Comme vous le verrez, la chose entière par valeur / par référence est très liée à ce que vous faites exactement avec ce tableau dans la portée de votre méthode / fonction: le lire ou le modifier?

Que dit PHP? (alias "en termes de changement")

Le manuel dit ceci (soulignement le mien):

Par défaut, les arguments de fonction sont passés par valeur (de sorte que si la valeur de l'argument dans la fonction est modifiée , elle ne soit pas modifiée en dehors de la fonction). Pour permettre à une fonction de modifier ses arguments, ils doivent être passés par référence .

Pour qu'un argument d'une fonction soit toujours transmis par référence, ajoutez une esperluette (&) au nom de l'argument dans la définition de la fonction

Pour autant que je sache, lorsque de gros programmeurs sérieux et honnêtes envers Dieu parlent de références, ils parlent généralement de modifier la valeur de cette référence . Et c'est exactement ce que les pourparlers manuels sur: hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Il y a un autre cas qu'ils ne mentionnent pas: que se passe-t-il si je ne change rien - lisez simplement?
Que se passe-t-il si vous passez un tableau à une méthode qui ne marque pas explicitement une référence et que nous ne changeons pas ce tableau dans la portée de la fonction? Par exemple:

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

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

echo readAndDoStuffWithAnArray($x);

Continuez à lire, mon compagnon de voyage.

Que fait réellement PHP? (aka "mémoire-sage")

Les mêmes programmeurs gros et sérieux, quand ils deviennent encore plus sérieux, ils parlent d '"optimisations de mémoire" en ce qui concerne les références. PHP aussi. Parce PHP is a dynamic, loosely typed language, that uses copy-on-write and reference countingque c'est pour ça .

Il ne serait pas idéal de passer des tableaux HUGE à diverses fonctions, et PHP pour en faire des copies (c'est ce que fait "pass-by-value", après tout):

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

Eh bien maintenant, si c'était réellement une valeur de passage, nous aurions perdu 3 Mo de RAM, car il y a deux copies de ce tableau, non?

Faux. Tant que nous ne changeons pas la $arrvariable, c'est une référence, en termes de mémoire . Vous ne le voyez tout simplement pas. C'est pourquoi PHP mentionne les références utilisateur-terre lors de la discussion &$someVar, pour distinguer les références internes et explicites (avec esperluette).

Les faits

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

J'ai trouvé trois (ouais, trois) cas:
a) la méthode / fonction lit uniquement l'argument tableau
b) la méthode / fonction modifie l'argument tableau
c) l' argument méthode / fonction tableau est explicitement marqué comme référence (avec un esperluette)


Tout d'abord, voyons combien de mémoire ce tableau consomme réellement (exécutez ici ):

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

Que de nombreux octets. Génial.

a) la méthode / fonction lit uniquement l'argument tableau

Faisons maintenant une fonction qui ne lit que ledit tableau comme argument et nous verrons combien de mémoire la logique de lecture prend:

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

Tu veux deviner? J'en ai 80! Voyez par vous-même . C'est la partie que le manuel PHP omet. Si le $arrparamètre était réellement passé par valeur, vous verriez quelque chose de similaire aux 1331840octets. Il semble que cela $arrse comporte comme une référence, n'est-ce pas? C'est parce qu'il est une référence - un interne.

b) la méthode / fonction modifie l'argument tableau

Maintenant, écrivons à ce paramètre, au lieu de le lire:

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

Encore une fois, voyez par vous - même , mais, pour moi, c'est assez proche de 1331840. Donc, dans ce cas, le tableau est en fait copié $arr.

c) l'argument tableau méthode / fonction est explicitement marqué comme référence (avec une esperluette)

Voyons maintenant combien de mémoire prend une opération d'écriture sur une référence explicite (exécutez ici ) - notez l'esperluette dans la signature de la fonction:

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

Je parie que vous obtenez 200 max! Cela consomme donc autant de mémoire que la lecture d'un paramètre non-esperluette .

Nevermind
la source
M'a sauvé quelques heures dans le débogage d'une fuite de mémoire!
Ragen Dazs du
2
Kosta Kontos: C'est une question tellement importante que vous devez la marquer comme réponse acceptée. Cela dit, @nevvermind: Excellent essai, mais veuillez inclure une section TL; DR supérieure.
AVIDeveloper
1
@nevvermind: Je ne suis pas un acronyme groopy, la principale différence est que les conclusions apparaissent généralement à la fin d'un article, tandis que TL; DR apparaît comme la première ligne pour ceux qui ont juste besoin de répondre brièvement au lieu de passer par une longue analyse . Votre recherche est bonne et ce n'est pas une critique, juste mon 00.02 $.
AVIDeveloper
1
Vous avez raison. J'ai mis les conclusions en haut. Mais j'aimerais quand même que les gens cessent d'être paresseux à lire le tout, avant d'arriver à une conclusion . Le défilement est trop facile pour nous de prendre la peine de changer l'ordre des choses.
Nevvermind
1
Je suppose que PHP est devenu plus efficace des années plus tard parce que vos exemples de codepad donnent des nombres beaucoup plus bas :)
drzaus
14

Par défaut

  1. Les primitives sont passées par valeur. Peu probable pour Java, la chaîne est primitive en PHP
  2. Les tableaux de primitives sont passés par valeur
  3. Les objets sont passés par référence
  4. Les tableaux d'objets sont transmis par valeur (le tableau) mais chaque objet est transmis par référence.

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

Remarque: En tant qu'optimisation, chaque valeur unique est transmise comme référence jusqu'à sa modification à l'intérieur de la fonction. S'il est modifié et que la valeur a été transmise par référence, il est copié et la copie est modifiée.

magallanes
la source
4
Cette réponse devrait être +1 en haut. Il contient un gotcha obscur que les autres réponses ne mentionnent pas: "4 - Les tableaux d'objets sont passés par valeur (le tableau) mais chaque objet est passé par référence." Je me grattais la tête à cause de celle-là!
August
@magallanes great devrait être évalué en premier pour moi aussi, vous me clarifiez un problème de tableau d'objets que j'avais. Existe-t-il un moyen de modifier un objet dans un tableau uniquement dans l'une des deux variables du tableau (l'original et la copie)?
fede72bari
5

Lorsqu'un tableau est passé à une méthode ou une fonction en PHP, il est passé par valeur à moins que vous ne le passiez explicitement par référence, comme ceci:

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

Dans votre deuxième question, ce $bn'est pas une référence à $a, mais une copie de $a.

Tout comme le premier exemple, vous pouvez faire référence $aen procédant comme suit:

$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
la source
1

Ce fil est un peu plus ancien mais voici quelque chose que je viens de rencontrer:

Essayez ce code:

$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

Notez qu'il n'y a pas d'ampli pour le paramètre $ params et qu'il change toujours la valeur de $ arr ['date']. Cela ne correspond pas vraiment à toutes les autres explications ici et à ce que je pensais jusqu'à présent.

Si je clone l'objet $ params ['date'], la 2ème date sortie reste la même. Si je le mets juste sur une chaîne, cela n'affecte pas non plus la sortie.

voleur
la source
3
Le tableau est copié, mais pas une profonde copie. Cela signifie que les valeurs primitives comme les nombres et les chaînes sont copiées dans $ param, mais pour les objets, la référence est copiée au lieu de l'objet cloné. $ arr contient une référence à $ date, tout comme le tableau copié $ params. Ainsi, lorsque vous appelez une fonction sur $ params ['date'] qui modifie sa valeur, vous modifiez également $ arr ['date'] et $ date. Lorsque vous définissez $ params ['date'] sur une chaîne, vous remplacez simplement la référence de $ params à $ date par autre chose.
ejegg
1

Pour étendre l'une des réponses, les sous-tableaux de tableaux multidimensionnels sont également passés par valeur, sauf s'ils sont passés explicitement par référence.

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

Le résultat est:

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
la source
0

En PHP, les tableaux sont passés aux fonctions par valeur par défaut, sauf si vous les passez explicitement par référence, comme le montre l'extrait de code suivant:

$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

Voici la sortie:

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