EDA Playground'da Dene

Deep Copy vs Shallow Copy

Gün 2: Nesne Yönelimli Programlama (OOP) | Derin kopya ve sığ kopya arasındaki farklar

Bu derste nesne kopyalamanın üç farklı seviyesini öğreneceğiz: handle ataması (alias), sığ kopya (shallow copy) ve derin kopya (deep copy). Bir nesnenin içinde başka bir nesneye referans (handle) bulunduğunda bu üç yaklaşımın çok farklı sonuçlar verdiğini inceleyeceğiz; bu, doğrulamada gizli ve sinsi hataların en sık kaynaklarından biridir.

01 — ÜÇ KOPYA, ÜÇ FARKLI KADER

“Kopyaladım” demek yetmez.

Bir handle sadece bir adrestir. Kopyalamanın üç seviyesi, heap'te kaç nesne olduğunu ve neyin paylaşıldığını belirler.

KOPYA DEĞİL

alias

b = a; Yeni nesne yok. Tek nesneye iki etiket yapıştı.

YARI YOL

shallow

Packet çoğalır, ama içindeki handle kopyalanır — Payload ortak kalır.

TAM BAĞIMSIZ

deep

Packet de Payload da yeniden doğar; içerik tek tek kopyalanır.

02 — BELLEK LABORATUVARI

Heap'i canlı izle.

Bir mod seç, Kopyala'ya bas, sonra kopya üzerinden nesneyi değiştir. Orijinalin başına ne geldiğini bellekte gör.

MOD
Bir mod seç ve Kopyala'ya bas.

KAYNAK KODSystemVerilog

SONUÇ TABLOSUcanlı

original == b—
original.payload == b.payload—
Orijinal Packet (id, name)—
Orijinal Payload (data)—

SİMÜLATÖR ÇIKTISI$display

03 — GERÇEK HAYAT TUZAĞI

Kuyruktaki herkes aynı kişi olunca.

Generator döngüde tx.randomize() yapıp aynı handle'ı kuyruğa koyarsa ne olur? Driver'ın gördüğü şey seni şaşırtabilir.

PUSH
Oynat'a bas: 3 kez randomize + push_back.

KAYNAK KODGenerator

SONUÇkuyruk içeriği

—
04 — MEYDAN OKUMA

Orijinal etkilenir mi?


    
Bu kod çalışınca original nesnesi değişir mi?
SERİ 0 · EN İYİ 0
05 — ÖZET

Aklında kalsın.

b = a → nesne yokYalnızca handle kopyalanır. Her değişiklik iki isimden de görülür.
shallow → Packet ayrı, Payload ortakSkaler alanlar bağımsız; iç nesne handle'ı paylaşılır.
deep → hiçbir şey paylaşılmazİç nesneler yeniden yaratılıp içerik tek tek kopyalanır.
Kuyruğa koymadan önce cloneAynı handle'ı döngüde push etmek, kuyruğu tek nesnenin N kopyası yapar.
UVM'de: do_copy() / clone()Alt nesne içeren transaction'larda do_copy içinde derin kopyayı sen yazarsın.
Handle içeren her sınıf sorar“Kopyalanınca bu alan paylaşılsın mı, çoğalsın mı?” sorusunu bilinçli cevapla.

Üç Kopyalama Seviyesi

Bir handle, nesneye işaret eden bir adres olduğundan "kopyalamak" tek anlama gelmez:

  • Handle ataması (Alias): b = a; yalnızca handle'ı kopyalar; yeni nesne oluşmaz. a ve b aynı nesneyi işaret eder; biri üzerinden yapılan her değişiklik diğerinden de görülür. Bu aslında bir kopya değildir.
  • Sığ kopya (Shallow Copy): Yeni bir nesne oluşturulur ve alanlar tek tek kopyalanır. Ancak bir alan başka bir nesneye handle ise, yalnızca handle kopyalanır; iç nesne paylaşılır. Yani üst nesne ayrıdır ama alt nesne ortaktır.
  • Derin kopya (Deep Copy): Yeni bir nesne oluşturulur ve içindeki alt nesneler de yeniden oluşturulup içerikleriyle kopyalanır. Sonuçta tamamen bağımsız, iç içe hiçbir şeyi paylaşmayan bir kopya elde edilir.

Neden Önemli?

Bir transaction nesnesini bir kuyruğa koyup sonra orijinali değiştirirseniz, sığ kopya kullandığınızda paylaşılan iç nesneler beklenmedik şekilde birlikte değişir. Bu tür hatalar derleyici tarafından yakalanmaz ve hata ayıklaması zordur. Bu yüzden hangi seviyenin gerektiğini bilinçli seçmek kritiktir.

Kaynak Kod

// =============================================================================
// GUN 2 - Konu 7: Deep Copy vs Shallow Copy vs Handle Atamasi (Alias)
// =============================================================================

class Payload;
  int data[$];

  function new();
    data = {};
  endfunction

  function void add(int val);
    data.push_back(val);
  endfunction

  function void display(string prefix = "");
    $display("%sPayload@%p data=%p", prefix, this, data);
  endfunction
endclass

class Packet;
  int     id;
  string  name;
  Payload payload;  // baska bir nesneye referans (handle)

  // SADE: Her Packet kendi payload'ini varsayilan olarak yaratir
  function new(int id = 0, string name = "");
    this.id      = id;
    this.name    = name;
    this.payload = new();
  endfunction

  // Shallow Copy: Yeni Packet, ama payload handle'i paylasilir
  function Packet shallow_copy();
    Packet p = new(this.id, this.name);
    p.payload = this.payload;  // payload ortak (referans paylasimi)
    return p;
  endfunction

  // Deep Copy: Yeni Packet + Yeni Payload + icerik kopyasi
  function Packet deep_copy();
    Packet p = new(this.id, this.name); // yeni payload
    foreach (this.payload.data[i])
      p.payload.add(this.payload.data[i]);
    return p;
  endfunction

  function void display(string prefix = "");
    $display("%sPacket@%p: id=%0d, name=%s, payload@%p",
             prefix, this, id, name, payload);
    payload.display({prefix, "  "});
  endfunction
endclass

module deep_copy_vs_shallow_copy;
  function void build_original(output Packet p);
    p = new(1, "orijinal");
    p.payload.add(10);
    p.payload.add(20);
    p.payload.add(30);
  endfunction

  initial begin
    Packet original, alias_h, shallow, deep;

    $display("=== Deep Copy vs Shallow Copy vs Handle Atamasi ===\n");

    // -------------------------------------------------------------------------
    // 0) Orijinal nesne
    // -------------------------------------------------------------------------
    build_original(original);
    $display("--- Orijinal ---");
    original.display("  ");

    // -------------------------------------------------------------------------
    // 1) Handle atamasi (ALIAS) - kopya degil!
    // -------------------------------------------------------------------------
    $display("\n--- 1) Handle Atamasi (Alias) [KOPYA DEGIL!] ---");
    alias_h = original; // sadece handle kopyalanir, nesne DEGIL

    alias_h.id   = 2;
    alias_h.name = "alias";
    alias_h.payload.add(999);

    $display("  original == alias_h ? %s", (original == alias_h) ? "EVET (ayni nesne)" : "HAYIR");
    original.display("  Original: ");
    alias_h.display("  Alias:    ");
    $display("  ^^^ Tek nesne var; her sey ortak degisti");

    // -------------------------------------------------------------------------
    // 2) Shallow copy - yeni Packet, ama payload ortak
    // -------------------------------------------------------------------------
    $display("\n--- 2) Shallow Copy (Yeni Packet, Paylasilan Payload) ---");
    build_original(original);

    shallow = original.shallow_copy();
    shallow.id   = 3;
    shallow.name = "shallow";
    shallow.payload.add(777);

    $display("  original == shallow ? %s", (original == shallow) ? "EVET" : "HAYIR (farkli Packet)");
    $display("  original.payload == shallow.payload ? %s",
             (original.payload == shallow.payload) ? "EVET (payload ortak)" : "HAYIR");

    original.display("  Original: ");
    shallow.display("  Shallow:  ");
    $display("  ^^^ Packet ayri, ama payload ortak oldugu icin payload degisikligi orijinali etkiler");

    // -------------------------------------------------------------------------
    // 3) Deep copy - yeni Packet, yeni payload, icerik kopyalanir
    // -------------------------------------------------------------------------
    $display("\n--- 3) Deep Copy (Yeni Packet, Yeni Payload) ---");
    build_original(original);

    deep = original.deep_copy();
    deep.id   = 4;
    deep.name = "deep";
    deep.payload.add(888);

    $display("  original == deep ? %s", (original == deep) ? "EVET" : "HAYIR (farkli Packet)");
    $display("  original.payload == deep.payload ? %s",
             (original.payload == deep.payload) ? "EVET" : "HAYIR (payload ayri)");

    original.display("  Original: ");
    deep.display("  Deep:     ");
    $display("  ^^^ Payload ayri oldugu icin orijinal etkilenmez");

    $display("\n=== Demo Sonu ===");
    $finish;
  end
endmodule

Kodun Açıklaması

  • Payload sınıfı: İçinde bir data kuyruğu (int [$]) tutar. add() ile eleman eklenir, display() ile this adresi (%p) ve içerik yazdırılır. Adres çıktısı, iki handle'ın aynı nesneyi mi yoksa farklı nesneleri mi gösterdiğini gözle ayırt etmeyi sağlar.
  • Packet sınıfı: id, name ve bir Payload payload handle'ı içerir. Constructor (new), her Packet için this.payload = new() ile kendi payload'ını yaratır.
  • shallow_copy(): Yeni bir Packet oluşturur ama p.payload = this.payload; ile payload handle'ını paylaştırır. Sonuçta iki Packet farklıdır, fakat aynı Payload nesnesini gösterir.
  • deep_copy(): Yeni bir Packet oluşturur (constructor zaten yeni bir payload yaratır) ve foreach ile orijinal payload'ın elemanlarını yeni payload'a tek tek kopyalar. Böylece payload da bağımsızdır.
  • 1) Alias testi: alias_h = original; sadece handle kopyalar. alias_h.id = 2 ve alias_h.payload.add(999) doğrudan orijinali etkiler; original == alias_h karşılaştırması EVET (ayni nesne) döner.
  • 2) Shallow testi: shallow = original.shallow_copy(); sonrası shallow.id = 3 orijinali etkilemez (Packet'ler ayrı), ama shallow.payload.add(777) orijinalin payload'ına da yansır çünkü original.payload == shallow.payload EVET'tir.
  • 3) Deep testi: deep = original.deep_copy(); sonrası deep.payload.add(888) orijinali etkilemez; original.payload == deep.payload HAYIR'dır, yani payload'lar tamamen ayrıdır.
  • build_original(): Her test öncesi output Packet p argümanıyla orijinali sıfırdan kurar; böylece testler birbirinden bağımsız temiz veriyle başlar.

Önemli Noktalar

  • Handle ataması (=) kopya değildir; iki değişken aynı nesneyi paylaşır. Gerçek bağımsızlık istiyorsanız mutlaka bir kopyalama metodu yazın.
  • Sığ kopyada üst nesne ayrı, ama içindeki handle alanları paylaşılır. İç nesnede yapılan değişiklik tüm kopyalardan görülür; bu çoğu zaman istenmeyen bir yan etkidir.
  • Derin kopyada iç nesneler de yeniden oluşturulur. Tam bağımsızlık gerektiğinde (örneğin bir transaction'ı kuyruğa koyup orijinali tekrar kullanırken) derin kopya tercih edilir.
  • Bir sınıfın başka nesne handle'ları içerdiği her durumda kopyalama davranışını bilinçli tasarlayın; aksi halde sığ kopya kaynaklı hatalar sessizce ortaya çıkar.
  • %p biçimlendirmesi nesne ve veri yapılarının içeriğini/kimliğini incelemekte pratiktir ve kopya seviyelerini gözlemlemeyi kolaylaştırır.
  • UVM'de bu kavram clone()/copy() ve do_copy() ile karşımıza çıkar; doğru derin kopya, yeniden kullanılabilir ve güvenilir transaction'lar için şarttır.
  • SystemVerilog'un yerleşik bir sığ kopya kısayolu vardır: Packet p2 = new p1; (copy constructor söz dizimi). Bu, p1'in tüm alanlarını yeni nesneye kopyalar ama iç handle'ları paylaşır; yani tam olarak shallow_copy()'nin yaptığı işi tek satırda yapar. Derin kopya için ise her zaman kendi metodunuzu yazmanız gerekir.
  • original == alias_h karşılaştırması handle eşitliğidir: iki handle aynı nesneyi mi gösteriyor? İçerik eşitliği için compare() gibi bir metot yazmanız gerekir; iki ayrı ama aynı içerikli nesne için == daima 0 döner.

Üç Seviyeyi Tek Bakışta Görmek

Handle ataması (b = a) Sığ kopya (new a / alan alan kopya) Derin kopya
Yeni Packet nesnesi oluşur mu? ❌ ✅ ✅
Yeni Payload nesnesi oluşur mu? ❌ ❌ (paylaşılır) ✅
b.id = 5 orijinali etkiler mi? Evet Hayır Hayır
b.payload.add(1) orijinali etkiler mi? Evet Evet Hayır
Maliyet Sıfır Düşük İç nesne sayısıyla artar
Ne zaman doğru? Aynı nesneyi paylaşmak istediğinizde İç nesne salt-okunur/paylaşılabilirse Tam bağımsızlık gerektiğinde

Benzetme: Bir belgenin kısayolunu masaüstüne koymak handle atamasıdır; kısayoldan açıp değiştirirseniz asıl belge değişir. Belgeyi kopyalamak ama içindeki bağlantılı Excel tablosunu aynı dosyaya bağlı bırakmak sığ kopyadır; Word dosyası ayrı, tablo ortak. Hem belgeyi hem bağlantılı tabloyu kopyalayıp bağlantıyı yeni tabloya çevirmek derin kopyadır.

Doğrulamada Gerçek Bir Senaryo

Bir driver, aldığı transaction'ı scoreboard'a gönderip ardından aynı nesneyi bir sonraki işlem için yeniden kullanıyor olsun:

// YANLIS: scoreboard ile driver ayni nesneyi paylasir
drv2scb.put(txn);        // handle gonderildi
txn.payload.add(255);    // scoreboard'daki "beklenen" de degisti!

// DOGRU: scoreboard'a bagimsiz bir kopya gider
drv2scb.put(txn.deep_copy());
txn.payload.add(255);    // orijinal degisir, scoreboard etkilenmez

Bu hata derleyici tarafından yakalanmaz ve genellikle "scoreboard rastgele uyuşmazlık veriyor" şeklinde kendini gösterir. Gün 6–7 projesinde driver'ın txn.copy() ile kopya gönderdiğini bu nedenle göreceksiniz.

Sık Yapılan Hatalar

  • copy() içinde iç handle'ı doğrudan atamak: p.payload = this.payload; tam da sığ kopya tuzağıdır. Derin kopya istiyorsanız iç nesne için de new + içerik kopyası gerekir.
  • Kuyruk/dizi alanlarını unutmak: int data[$] gibi alanlar değer tipidir ve = ile kopyalanır; ama bir Payload handle'ı kopyalanmaz. Alanın tipi (değer mi handle mı?) kopyalama davranışını belirler.
  • Kalıtım zincirinde copy()'yi güncellemek: Alt sınıf yeni alan eklediğinde copy()'yi override edip super.copy() sonucunu $cast ile alıp kendi alanlarını kopyalaması gerekir; aksi halde yeni alanlar kopyada eksik kalır.
  • new a söz dizimini derin kopya sanmak: Yalnızca sığ kopyadır.

Kendinizi Deneyin

  1. shallow_copy() yerine Packet p = new this; tek satırını kullanan shallow_copy2() yazın ve çıktının aynı olduğunu doğrulayın.
  2. Payload sınıfına string tag; alanı ekleyin. deep_copy()'yi güncellemeden çalıştırın: tag kopyaya geçti mi? Sonra düzeltin.
  3. Packet'ten PriorityPacket extends Packet türetip int prio; ekleyin ve deep_copy()'yi override edin (ipucu: super.deep_copy() + $cast).
  4. compare(Packet other) metodu yazın; deep.compare(original) sonucunun deep.payload.add(888) çağrısından önce 1, sonra 0 olduğunu gösterin.
Hızlı Kontrol: Sığ kopyadan sonra shallow.payload.add(7) çağrısı original.payload'ı etkiler mi?

Evet. Sığ kopyada Packet nesnesi yenidir ama payload handle'ı kopyalanmıştır; iki Packet aynı Payload nesnesini gösterir. Bağımsız payload için derin kopya gerekir.