Alokasi yang eksplisit menimbulkan pertanyaan yang harus dijawab setiap kali memori diperoleh, yaitu siapa yang membebaskannya. Zig tidak memiliki pengumpul sampah maupun pemeriksa kepemilikan, sehingga jawabannya ditetapkan oleh penulis kode dan dituliskan sebagai kontrak. Topik ini membahas aturan kepemilikan satu blok satu pemilik, peran fungsi peminjam dan fungsi pemilik, cara menuliskan kontraknya pada dokumentasi, serta program yang memindahkan kepemilikan dari fungsi ke pemanggilnya.
Konsep#
Satu alokasi, satu pemilik#
Kepemilikan memori adalah tanggung jawab tunggal atas nasib sebuah blok, mencakup kapan ia dibebaskan dan oleh siapa. Aturan dasarnya sederhana, yaitu setiap blok mempunyai tepat satu pemilik pada setiap saat. Bila pemiliknya tidak ada, blok tidak pernah dibebaskan dan memorinya bocor, sedangkan bila pemiliknya lebih dari satu, blok dapat dibebaskan dua kali dengan akibat yang tidak terdefinisi.
Pemilik tidak selalu pihak yang mengalokasikan. Yang menentukan adalah siapa yang menanggung pembebasan setelah kepemilikan berpindah, dan perpindahan itu terjadi secara eksplisit, misalnya saat sebuah fungsi mengembalikan slice hasil alokasi. Zig menyerahkan pembukuan ini kepada penulisnya, tetapi bukan tanpa pengawasan, karena pemeriksa kebocoran pada waktu pengujian menangkap blok yang kehilangan pemiliknya.
Aturan satu pemilik menjelaskan dua gejala yang paling sering dikeluhkan pada kode yang baru belajar mengalokasikan. Kebocoran muncul ketika kepemilikan hilang di tengah jalan, misalnya slice hasil alokasi ditimpa sebelum sempat dibebaskan. Kerusakan memori muncul ketika dua pihak memperlakukan dirinya sebagai pemilik, sehingga free dijalankan dua kali atas blok yang sama.
Fungsi peminjam dan fungsi pemilik#
Kontrak kepemilikan sebuah fungsi terbaca dari tanda tangannya. Fungsi peminjam menerima data yang sudah ada, memakainya, dan mengembalikan hasil yang tidak memerlukan pembebasan, sehingga parameternya berupa slice []const T atau []T. Fungsi pemilik menerima allocator, memperoleh memori baru, dan mengembalikan slice yang wajib dibebaskan pemanggilnya. Kedua peran tersebut dapat hadir dalam satu program tanpa saling mencampuri urusan.
| Ciri | Fungsi peminjam | Fungsi pemilik |
|---|---|---|
| Parameter | slice []const T atau []T | Allocator beserta data sumber |
| Memori baru | tidak diperoleh | diperoleh melalui alloc atau dupe |
| Pembebasan | tidak pernah | menjadi tanggung jawab pemanggil |
| Nilai kembalian | nilai biasa atau error | slice baru atau error |
Pembagian ini menjelaskan mengapa banyak fungsi pustaka standar menerima []const T alih-alih tipe yang lebih rumit. Peminjaman membuat fungsi dapat dipakai atas data milik siapa pun tanpa menambah satu kewajiban pembebasan pun bagi pemanggilnya. Sebaliknya, setiap fungsi pemilik menambah satu kewajiban baru pada sisi pemanggil, sehingga jumlahnya patut dijaga agar tetap sedikit.
// tidak-dijalankan
fn panjang_label(teks: []const u8) usize {
return teks.len;
}
fn salin_label(alloc: std.mem.Allocator, sumber: []const u8) ![]u8 {
return try alloc.dupe(u8, sumber);
}
// panjang_label hanya membaca, salin_label mengembalikan memori baruKontrak pada dokumentasi#
Kontrak kepemilikan wajib dinyatakan secara tertulis, dan tempatnya adalah komentar dokumentasi /// di atas fungsi. Entri yang cukup menyebut tiga hal, yaitu fungsi memperoleh memori baru, pemanggil yang membebaskan hasilnya, dan allocator mana yang dipakai pada pembebasan tersebut. Dokumentasi pustaka standar menuliskan kontrak semacam ini pada fungsi-fungsinya, sehingga kebiasaan yang sama patut diikuti pada kode sendiri.
// tidak-dijalankan
/// Mengembalikan salinan baru dari teks sumber.
/// Pemanggil wajib membebaskan hasilnya dengan alloc.free().
fn salin_label(alloc: std.mem.Allocator, sumber: []const u8) ![]u8 {
return try alloc.dupe(u8, sumber);
}Konvensi penamaan membantu kontrak terbaca dengan cepat. Nama dupe pada pustaka standar menandakan salinan baru yang menjadi milik pemanggil, sedangkan fungsi yang hanya memproses masukan tidak pernah menyentuh masa hidup parameternya. Ketika kontrak dan penamaan konsisten, pembaca kode dapat memastikan banyaknya pembebasan yang wajib ditulis hanya dari tanda tangan dan dokumentasinya.
Contoh Lengkap#
Program: duplikat yang dipindahkan ke pemanggil#
Program berikut memuat kedua peran sekaligus. Fungsi salin_label adalah pemilik yang mengembalikan salinan baru, sedangkan panjang_label adalah peminjam yang hanya membaca slice yang diterimanya. Kontras keduanya memperlihatkan perpindahan kepemilikan dari fungsi ke pemanggil serta kerja peminjam tanpa kewajiban pembebasan.
const std = @import("std");
fn salin_label(alloc: std.mem.Allocator) ![]u8 {
return try alloc.dupe(u8, "rekap-gudang");
}
fn panjang_label(teks: []const u8) usize {
return teks.len;
}
pub fn main() !void {
const alloc = std.heap.page_allocator;
const label = try salin_label(alloc);
defer alloc.free(label);
std.debug.print("label: {s}\n", .{label});
std.debug.print("panjang menurut peminjam: {d}\n", .{panjang_label(label)});
}label: rekap-gudang
panjang menurut peminjam: 12Pada baris const label = try salin_label(alloc), kepemilikan salinan berpindah dari fungsi ke main. Dari titik itu, main menjadi satu-satunya pihak yang berhak membebaskannya, dan defer alloc.free(label) menunaikan kewajiban tersebut pada akhir fungsi. Pemanggilan panjang_label(label) tidak mengubah pembukuan apa pun, karena slice yang diteruskan tetap milik main dan peminjamnya cukup membaca teks.len.
# mengompilasi dan menjalankan program
zig run salin-label.ziglabel: rekap-gudang
panjang menurut peminjam: 12Kesalahan umum#
- Memori dibebaskan dua kali karena dua pihak merasa menjadi pemiliknya. Akibatnya tidak terdefinisi dan dapat berupa penghentian program. Tetapkan satu pemilik pada dokumentasi, lalu hapus pembebasan yang tersisa.
- Fungsi peminjam membebaskan slice yang diterimanya. Kontrak terlanggar karena peminjam tidak pernah menjadi pemilik, dan pemilik aslinya menghadapi memori yang sudah tidak sah. Hapus
freedari tubuh fungsi peminjam. - Fungsi mengembalikan alamat variabel lokalnya. Variabel lokal berakhir saat fungsi kembali, dan Zig menolak kode ini pada waktu kompilasi dengan pesan returning address of expired local variable. Kembalikan salinan yang dialokasikan, atau teruskan buffer milik pemanggil sebagai parameter.
Ringkasan#
- Setiap blok memori mempunyai tepat satu pemilik, dan pemilik itulah yang membebaskannya.
- Fungsi peminjam menerima slice dan tidak pernah membebaskannya, sedangkan fungsi pemilik mengembalikan memori baru yang nasibnya menjadi tanggung jawab pemanggil.
- Kontrak kepemilikan ditulis pada komentar dokumentasi fungsi dan dipercepat pembacaannya oleh konvensi penamaan seperti
dupe. deferpada pemanggil menegakkan kontrak tersebut pada semua jalur keluar fungsi.
Referensi#
- Memory — Zig Language Reference — Konvensi kepemilikan memori dan dokumen resmi aturan alokasinya.