PHP Foreach Passa per riferimento: duplicazione dell'ultimo elemento? (Bug?)


159

Ho appena avuto un comportamento molto strano con un semplice script php che stavo scrivendo. L'ho ridotto al minimo necessario per ricreare il bug:

<?php

$arr = array("foo",
             "bar",
             "baz");

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

?>

Questo produce:

Array
(
    [0] => foo
    [1] => bar
    [2] => baz
)
Array
(
    [0] => foo
    [1] => bar
    [2] => bar
)

Si tratta di un bug o di un comportamento davvero strano che dovrebbe accadere?


Fallo di nuovo per valore, vedi se cambia la terza volta ...?
Shackrock,

1
@Shackrock, non sembra più cambiare con la ripetizione di loop in base al valore.
Regalità

1
È interessante notare che se cambi il secondo ciclo per usare qualcosa di diverso da $ item, allora funziona come previsto.
Steve Claridge,

9
disinserisci sempre l'elemento alla fine del loop: foreach($x AS &$y){ ... unset($y); }- in realtà è su php.net (non so dove) perché è un errore molto commesso.
Rudie,

Risposte:


170

Dopo il primo ciclo foreach, $itemè ancora presente un riferimento a un valore utilizzato anche da $arr[2]. Quindi ogni foreach call nel secondo loop, che non chiama per riferimento, sostituisce quel valore, e quindi $arr[2], con il nuovo valore.

Quindi loop 1, il valore e $arr[2]diventa $arr[0], che è 'pippo'.
Loop 2, il valore e $arr[2]diventa $arr[1], che è 'bar'.
Loop 3, il valore e $arr[2]diventa $arr[2], che è 'bar' (a causa del loop 2).

Il valore 'baz' viene effettivamente perso alla prima chiamata del secondo ciclo foreach.

Debug dell'output

Per ogni iterazione del ciclo, riecheggeremo il valore $iteme stamperemo ricorsivamente l'array $arr.

Quando viene eseguito il primo ciclo, vediamo questo output:

foo
Array ( [0] => foo [1] => bar [2] => baz )

bar
Array ( [0] => foo [1] => bar [2] => baz )

baz
Array ( [0] => foo [1] => bar [2] => baz )

Alla fine del ciclo, $itemindica ancora lo stesso posto di $arr[2].

Quando viene eseguito il secondo ciclo, vediamo questo output:

foo
Array ( [0] => foo [1] => bar [2] => foo )

bar
Array ( [0] => foo [1] => bar [2] => bar )

bar
Array ( [0] => foo [1] => bar [2] => bar )

Noterai come ogni volta che un array inserisce un nuovo valore $item, anch'esso aggiornato $arr[3]con lo stesso valore, poiché entrambi puntano ancora nella stessa posizione. Quando il ciclo arriva al terzo valore dell'array, conterrà il valore barperché era stato appena impostato dalla precedente iterazione di quel ciclo.

E 'un errore?

No. Questo è il comportamento di un elemento referenziato e non un bug. Sarebbe simile a eseguire qualcosa di simile:

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

Un ciclo foreach non è di natura speciale in cui può ignorare gli elementi di riferimento. Sta semplicemente impostando quella variabile sul nuovo valore ogni volta che faresti fuori da un ciclo.


4
Ho una leggera correzione pedante. $itemnon è un riferimento a $arr[2], il valore contenuto da $arr[2]è un riferimento al valore a cui fa riferimento $item. Per illustrare la differenza, potresti anche non essere impostato $arr[2]e $itemnon verrebbe influenzato, e scrivere per $itemnon influirebbe su di essa.
Paul Biggar,

2
Questo comportamento è complesso da comprendere e può causare problemi. Lo considero uno dei miei preferiti per mostrare ai miei studenti perché dovrebbero evitare (per quanto possono) cose "per riferimento".
Olivier Pons il

1
Perché non $itemesce dal campo di applicazione quando si esce dal ciclo foreach? Sembra un problema di chiusura?
Giovedì

6
@jocull: IN PHP, foreach, per, mentre, ecc. non creano il proprio ambito.
animuson

1
@jocull, PHP non ha (bloccare) variabili locali. Uno dei motivi per cui mi dà fastidio.
Qtax

29

$itemè un riferimento $arr[2]e viene sovrascritto dal secondo ciclo foreach, come sottolineato da animuson.

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

unset($item); // This will fix the issue.

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

3

Anche se questo potrebbe non essere ufficialmente un bug, secondo me lo è. Penso che il problema qui sia che ci aspettiamo che $itemesca dal campo di applicazione quando il ciclo viene chiuso come in molti altri linguaggi di programmazione. Tuttavia, questo non sembra essere il caso ...

Questo codice ...

$arr = array('one', 'two', 'three');
foreach($arr as $item){
    echo "$item\n";
}    
echo $item;

Fornisce l'output ...

one
two
three
three

Come già detto da altre persone, stai sovrascrivendo la variabile referenziata $arr[2]con il tuo secondo ciclo, ma sta succedendo solo perché $itemnon è mai andato fuori campo. Cosa ne pensate ragazzi ... bug?


4
1) Non un bug. È già richiamato nel manuale e archiviato in una serie di segnalazioni di bug come previsto. 2) Non risponde davvero alla domanda ...
BoltClock

Non mi ha sorpreso a causa del problema dell'ambito, mi aspettavo che $ item rimanesse in giro dopo il foreach iniziale, ma non mi rendevo conto che foreach UPDATES la variabile invece di SOSTITUIRE. ad es. come eseguire unset ($ item) prima del secondo ciclo. Si noti che unset non cancella il valore (e quindi l'ultimo elemento dell'array) rimuove semplicemente la variabile.
Programster,

Sfortunatamente, PHP non crea un nuovo ambito per loop o {}blocchi in generale. Ecco come funziona la lingua
Fabian Schmengler il


0

Il comportamento corretto di PHP potrebbe essere un errore di AVVISO a mio avviso. Se una variabile referenziata creata in un ciclo foreach viene utilizzata al di fuori del ciclo, dovrebbe generare un avviso. Molto facile cadere per questo comportamento, molto difficile individuarlo quando è successo. E nessuno sviluppatore leggerà la pagina della documentazione di foreach, non è un aiuto.

Dovresti fare unset()riferimento dopo il tuo ciclo per evitare questo tipo di problema. unset () su un riferimento rimuoverà semplicemente il riferimento senza danneggiare i dati originali.


0

questo perché si utilizza dalla direttiva ref (&). l'ultimo valore verrà sostituito dal secondo ciclo e corromperà l'array. la soluzione più semplice è usare un nome diverso per il secondo loop:

foreach ($arr as &$item) { ... }

foreach ($arr as $anotherItem) { ... }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.