Apakah array dalam PHP disalin sebagai nilai atau sebagai referensi ke variabel baru, dan ketika diteruskan ke fungsi?

259

1) Ketika sebuah array dilewatkan sebagai argumen ke metode atau fungsi, apakah dilewatkan oleh referensi, atau dengan nilai?

2) Saat menetapkan array ke suatu variabel, apakah variabel baru tersebut merupakan referensi ke array asli, atau apakah itu salinan baru?
Bagaimana dengan melakukan ini:

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

Apakah $breferensi untuk $a?

jujur
sumber
Juga lihat When-does-foreach-copy
nawfal
3
@ MarslonJerezIsla: sepertinya Array hanya dikloning jika Anda memodifikasinya di dalam fungsi. Masih berasal dari bahasa lain, sepertinya aneh.
user276648

Jawaban:

276

Untuk bagian kedua dari pertanyaan Anda, lihat halaman array manual , yang menyatakan (mengutip) :

Tugas array selalu melibatkan penyalinan nilai. Gunakan operator referensi untuk menyalin array dengan referensi.

Dan contoh yang diberikan:

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


Untuk bagian pertama, cara terbaik untuk memastikan adalah mencoba ;-)

Pertimbangkan contoh kode ini:

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

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

Ini akan memberikan output ini:

array
  0 => int 10
  1 => int 20

Yang menunjukkan fungsi belum mengubah array "luar" yang dilewatkan sebagai parameter: itu dilewatkan sebagai salinan, dan bukan referensi.

Jika Anda ingin itu dilewatkan dengan referensi, Anda harus memodifikasi fungsinya, dengan cara ini:

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

Dan hasilnya akan menjadi:

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

Seperti, kali ini, array telah lulus "dengan referensi".


Jangan ragu untuk membaca bagian yang Dijelaskan Referensi dalam manual: ini akan menjawab beberapa pertanyaan Anda ;-)

Pascal MARTIN
sumber
bagaimana dengan sesuatu seperti $ a = & $ this-> a. Apakah $ sekarang referensi ke & ini-> a?
Frank
1
Ketika Anda menggunakan &, ya, itu harus - lihat php.net/manual/en/…
Pascal MARTIN
1
sapi suci, saya tidak percaya ini adalah masalah yang saya miliki ... seandainya ini menjadi pelajaran, selalu baca manualnya
Heavy_Bullets
2
Hai Pascal, saya menemukan jawaban Kosta Kontos tampaknya lebih akurat. Saya melakukan tes cepat sederhana untuk mengonfirmasi temuannya gist.github.com/anonymous/aaf845ae354578b74906 Bisakah Anda mengomentari temuannya juga?
Cheok Yan Cheng
1
Ini adalah masalah yang saya alami juga: berpikir itu adalah sesuatu yang aneh tentang array bersarang tetapi sebenarnya cara kerja array berfungsi dalam PHP.
Daftar Jeremy
120

Sehubungan dengan pertanyaan pertama Anda, array dilewatkan dengan referensi KECUALI itu dimodifikasi dalam metode / fungsi yang Anda panggil. Jika Anda mencoba untuk memodifikasi array dalam metode / fungsi, salinannya dibuat terlebih dahulu, dan kemudian hanya salinan yang dimodifikasi. Ini membuatnya seolah-olah array dilewatkan oleh nilai padahal sebenarnya tidak.

Sebagai contoh, dalam kasus pertama ini, walaupun Anda tidak mendefinisikan fungsi Anda untuk menerima $ my_array dengan referensi (dengan menggunakan karakter & dalam definisi parameter), ia masih dilewatkan oleh referensi (yaitu: Anda tidak membuang-buang memori dengan salinan yang tidak perlu).

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
}

Namun jika Anda memodifikasi array, salinannya dibuat terlebih dahulu (yang menggunakan lebih banyak memori tetapi membuat array asli Anda tidak terpengaruh).

function handle_array($my_array) {

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

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

FYI - ini dikenal sebagai "copy malas" atau "copy-on-write".

Kosta Kontos
sumber
8
Ini adalah informasi yang sangat menarik! Sepertinya itu benar; tetapi saya tidak dapat menemukan dokumentasi resmi yang mendukung fakta ini. Kita juga perlu tahu versi PHP mana yang mendukung konsep salin malas ini. Adakah yang punya info lebih lanjut?
Mario Awad
8
Perbarui, temukan beberapa dokumentasi resmi, masih perlu menemukan versi PHP mana yang mendukung salinan malas (mereka menyebutnya "copy on write" dalam manual): php.net/manual/en/internals2.variables.intro.php
Mario Awad
7
Ini murni keputusan implementasi dari mesin virtual PHP, dan bukan bagian dari bahasa - itu sebenarnya tidak terlihat oleh programmer. Copy-on-write tentu saja direkomendasikan untuk alasan kinerja, tetapi implementasi yang menyalin setiap array tidak memiliki perbedaan dari perspektif pemrogram, sehingga kita dapat mengatakan bahwa semantik bahasa menentukan pass-by-value.
Superfly
14
@Superfly tentu saja membuat perbedaan ketika saya ingin tahu apakah saya bisa melewatkan array 100MB saya melalui tumpukan lusinan fungsi tanpa kehabisan memori! Anda mungkin benar bahwa itu adalah hak untuk memanggil semantik sebagai nilai tambah, tetapi mengesampingkan pertengkaran semacam itu atas terminologi, "detail implementasi" yang disebutkan di sini tentu saja penting bagi programmer PHP di dunia nyata.
Mark Amery
3
Ada kekhasan lain untuk ini, yang membuat menyadari copy-on-write bahkan lebih penting ketika memikirkan kinerja. Anda mungkin berpikir melewatkan array dengan referensi menghemat memori dibandingkan dengan melewati nilai (jika Anda tidak tahu tentang copy-on-write) tetapi sebenarnya bisa memiliki efek sebaliknya ! Jika array selanjutnya disahkan oleh nilai (oleh kode pihak Anda sendiri atau 3), PHP kemudian memiliki untuk membuat salinan lengkap atau tidak dapat lagi melacak jumlah referensi! Lebih lanjut di sini: stackoverflow.com/questions/21974581/…
Dan King
80

TL; DR

a) metode / fungsi hanya membaca argumen array => referensi implisit (internal)
b) metode / fungsi memodifikasi argumen array => nilai
c) argumen metode / fungsi array secara eksplisit ditandai sebagai referensi (dengan ampersand) => referensi eksplisit (lahan pengguna)

Atau ini:
- param array non-ampersand : disahkan oleh referensi; operasi penulisan mengubah salinan array yang baru, salinan yang dibuat pada penulisan pertama;
- parameter array ampersand : disahkan oleh referensi; operasi penulisan mengubah array asli.

Ingat - PHP melakukan copy- nilai saat Anda menulis ke param array non-ampersand. Itu copy-on-writeartinya. Saya ingin menunjukkan sumber C perilaku ini kepada Anda, tetapi menakutkan di sana. Lebih baik gunakan xdebug_debug_zval () .

Pascal MARTIN benar. Kosta Kontos bahkan lebih.

Menjawab

Tergantung.

Versi panjang

Saya pikir saya menulis ini untuk diri saya sendiri. Saya harus punya blog atau sesuatu ...

Setiap kali orang berbicara tentang referensi (atau petunjuk, dalam hal ini), mereka biasanya berakhir dengan gegabah (lihat saja utas ini !).
PHP menjadi bahasa yang terhormat, saya pikir saya harus menambahkan hingga kebingungan (meskipun ini ringkasan jawaban di atas). Karena, walaupun dua orang bisa benar pada saat yang sama, Anda lebih baik hanya memecahkan kepala bersama menjadi satu jawaban.

Pertama-tama, Anda harus tahu bahwa Anda bukan seorang yang jago jika Anda tidak menjawab dengan cara hitam-putih . Hal-hal lebih rumit daripada "ya / tidak".

Seperti yang akan Anda lihat, keseluruhan hal berdasarkan nilai / referensi adalah sangat terkait dengan apa sebenarnya yang Anda lakukan dengan array itu dalam metode / fungsi Anda: membacanya atau memodifikasinya?

Apa kata PHP? (alias "perubahan bijaksana")

The pengguna mengatakan ini (penekanan):

Secara default, argumen fungsi dilewatkan oleh nilai (sehingga jika nilai argumen di dalam fungsi diubah , itu tidak bisa diubah di luar fungsi). Untuk memungkinkan suatu fungsi memodifikasi argumennya, mereka harus dilewatkan dengan referensi .

Agar argumen ke suatu fungsi selalu dilewatkan oleh referensi, tambahkan sebuah ampersand (&) ke nama argumen dalam definisi fungsi

Sejauh yang saya tahu, ketika programmer besar, serius, jujur ​​kepada Tuhan berbicara tentang referensi, mereka biasanya berbicara tentang mengubah nilai referensi itu . Dan itulah yang pembicaraan pengguna tentang: hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Ada kasus lain yang tidak mereka sebutkan: bagaimana jika saya tidak mengubah apa pun - baca saja?
Bagaimana jika Anda meneruskan array ke metode yang tidak secara eksplisit menandai referensi, dan kami tidak mengubah array itu di lingkup fungsi? Misalnya:

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

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

echo readAndDoStuffWithAnArray($x);

Baca terus, sesama pelancong.

Apa yang sebenarnya dilakukan PHP? (alias "memori-bijaksana")

Pemrogram besar dan serius yang sama, ketika mereka menjadi lebih serius, mereka berbicara tentang "optimasi memori" dalam hal referensi. Begitu juga PHP. Karena PHP is a dynamic, loosely typed language, that uses copy-on-write and reference countingitu sebabnya .

Tidaklah ideal untuk melewatkan array BESAR ke berbagai fungsi, dan PHP untuk membuat salinannya (setelah itu apa yang dilakukan "pass-by-value"):

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

Nah sekarang, jika ini benar-benar nilai pass-by-value, kita akan memiliki 3MB + RAM hilang, karena ada dua salinan dari array itu, kan?

Salah. Selama kita tidak mengubah $arrvariabel, itu adalah referensi, berdasarkan ingatan . Anda hanya tidak melihatnya. Itu sebabnya PHP menyebutkan referensi pengguna-lahan ketika berbicara tentang &$someVar, untuk membedakan antara yang internal dan eksplisit (dengan ampersand).

Fakta

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

Saya datang dengan tiga (yeah, tiga) kasus:
a) metode / fungsi hanya membaca argumen array
b) metode / fungsi memodifikasi argumen array
c) argumen metode / fungsi array secara eksplisit ditandai sebagai referensi (dengan simbol untuk 'dan)


Pertama, mari kita lihat berapa banyak memori yang sebenarnya dimakan array (jalankan di sini ):

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

Itu banyak byte. Bagus.

a) metode / fungsi hanya membaca argumen array

Sekarang mari kita membuat fungsi yang hanya membaca array kata sebagai argumen dan kita akan melihat berapa banyak memori yang dibutuhkan oleh logika pembacaan:

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

Ingin menebak? Saya mendapat 80! Lihat sendiri . Ini adalah bagian yang dihilangkan manual PHP. Jika $arrparam benar-benar di-by-value, Anda akan melihat sesuatu yang mirip dengan 1331840byte. Tampaknya itu $arrberlaku seperti referensi, bukan? Itu karena itu adalah referensi - yang internal.

b) metode / fungsi memodifikasi argumen array

Sekarang, mari menulis ke param itu, alih-alih membacanya:

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

Sekali lagi, lihat sendiri , tapi, bagi saya, itu cukup dekat untuk menjadi 1331840. Jadi dalam hal ini, array yang sebenarnya sedang disalin ke $arr.

c) argumen metode / fungsi array secara eksplisit ditandai sebagai referensi (dengan ampersand)

Sekarang mari kita lihat berapa banyak memori yang dibutuhkan oleh operasi tulis untuk referensi eksplisit (jalankan di sini ) - perhatikan ampersand pada tanda tangan fungsi:

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

Taruhan saya adalah Anda mendapatkan 200 maks! Jadi ini memakan sekitar memori sebanyak membaca dari param non-ampersand .

nevvermind
sumber
Menyelamatkan saya beberapa jam dalam men-debug kebocoran memori!
Ragen Dazs
2
Kosta Kontos: Ini adalah pertanyaan penting yang harus Anda tandai sebagai jawaban yang diterima. Yang mengatakan, @nevvermind: Esai bagus, tapi harap sertakan bagian TL; DR.
AVIDeveloper
1
@nevvermind: Saya bukan akronim groopy, perbedaan utamanya adalah bahwa Kesimpulan biasanya muncul di akhir artikel, sedangkan TL; DR muncul sebagai baris pertama bagi mereka yang hanya perlu menjawab singkat alih-alih melalui analisis yang panjang. . Penelitian Anda bagus dan ini bukan kritik, hanya $ 00,02 saya.
AVIDeveloper
1
Kamu benar. Saya telah menempatkan kesimpulan di atas. Tapi saya masih ingin orang-orang berhenti malas membaca semuanya, sebelum mencapai kesimpulan apa pun . Menggulir terlalu mudah bagi kita untuk repot mengubah urutan sesuatu.
nevvermind
1
Saya kira PHP menjadi lebih efisien bertahun-tahun kemudian karena contoh codepad Anda memberikan angka yang jauh lebih rendah :)
drzaus
14

Secara default

  1. Primitif dilewatkan oleh nilai. Tidak seperti Java, string adalah primitif dalam PHP
  2. Susunan primitif dilewatkan oleh nilai
  3. Objek dilewatkan dengan referensi
  4. Array objek dilewatkan oleh nilai (array) tetapi setiap objek dilewatkan oleh referensi.

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

Catatan: Sebagai pengoptimalan, setiap nilai tunggal dilewatkan sebagai referensi hingga dimodifikasi di dalam fungsi. Jika diubah dan nilainya diteruskan dengan referensi, maka disalin dan salinannya dimodifikasi.

magallanes
sumber
4
Jawaban ini harus diberi +1 ke atas. Ini berisi gotcha yang tidak jelas yang jawaban lain tidak menyebutkan: "4 - Array objek dilewatkan oleh nilai (array) tetapi setiap objek dilewatkan oleh referensi." Aku menggaruk kepalaku karena itu!
augustin
@ magallanes hebat harus diberi peringkat pertama untuk saya juga, Anda menjelaskan masalah array objek yang saya miliki. Apakah ada cara untuk memodifikasi objek dalam array hanya di salah satu dari dua variabel array (asli dan salinannya)?
fede72bari
5

Ketika sebuah array dilewatkan ke metode atau fungsi dalam PHP, itu dilewatkan oleh nilai kecuali Anda secara eksplisit meneruskannya dengan referensi, seperti:

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

Dalam pertanyaan kedua Anda, $bbukan referensi $a, tetapi salinan $a.

Sama seperti contoh pertama, Anda dapat referensi $adengan melakukan hal berikut:

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

Utas ini sedikit lebih tua tetapi di sini sesuatu yang baru saja saya temui:

Coba kode ini:

$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

Perhatikan tidak ada amp untuk parameter $ params dan masih mengubah nilai $ arr ['date']. Ini tidak benar-benar cocok dengan semua penjelasan lain di sini dan apa yang saya pikirkan sampai sekarang.

Jika saya mengkloning objek $ params ['date'], tanggal ke-2 tetap sama. Jika saya hanya mengaturnya ke string, itu tidak mempengaruhi output.

Robbash
sumber
3
Array disalin, tetapi ini bukan salinan yang dalam . Ini berarti bahwa nilai-nilai primitif seperti angka dan string disalin ke $ param, tetapi untuk objek, referensi disalin daripada objek yang dikloning. $ arr memegang referensi ke $ date, dan begitu juga array $ params yang disalin. Jadi ketika Anda memanggil fungsi pada $ params ['date'] yang mengubah nilainya, Anda juga mengubah $ arr ['date'] dan $ date. Ketika Anda mengatur $ params ['date'] menjadi sebuah string, Anda hanya mengganti referensi $ params ke $ date dengan sesuatu yang lain.
ejegg
1

Untuk memperluas salah satu jawaban, sub array array multidimensi juga diberikan nilai kecuali jika diteruskan secara eksplisit oleh referensi.

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

Hasilnya adalah:

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

Dalam array PHP diteruskan ke fungsi dengan nilai secara default, kecuali Anda secara eksplisit meneruskannya dengan referensi, seperti yang diperlihatkan cuplikan berikut:

$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

Berikut hasilnya:

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