Memori dinamis pada Zig tidak pernah diambil secara diam-diam, melainkan diminta melalui antarmuka std.mem.Allocator. Topik ini membahas isi antarmuka tersebut, alokasi pertama dengan std.heap.page_allocator, konvensi parameter allocator pada fungsi, serta kepasangan antara alokasi dan pembebasan. Setelah topik ini dibaca, buffer dapat dialokasikan, diisi, dibaca, dan dibebaskan dengan pola yang menjadi dasar seluruh topik berikutnya.
Konsep#
Allocator sebagai antarmuka memori#
Tipe std.mem.Allocator adalah antarmuka, yaitu sekumpulan metode yang sama bagi setiap penyedia memori tanpa membedakan cara kerja di baliknya. Metode yang paling sering dipakai adalah alloc untuk memperoleh blok baru, free untuk mengembalikannya, dupe untuk menyalin data, dupeSentinel untuk salinan yang berakhir dengan nilai penanda, dan resize untuk mengubah panjang. Pemanggilan alloc(u8, 256) mengembalikan slice []u8 berpanjang 256 yang siap ditulis.
Alokasi memori dapat gagal karena memori terbatas, dan kegagalan itu dimodelkan sebagai error. Tipe kembalian alloc adalah error union ![]T yang membawa error.OutOfMemory bila permintaan tidak dapat dipenuhi, sehingga pemanggil wajib menanganinya, umumnya dengan try. Model ini menjaga alokasi tetap eksplisit pada dua sisi, yaitu penyedia memorinya pada parameter dan kemungkinan gagalnya pada tipe kembalian.
Zig tidak memiliki allocator bawaan yang bekerja tanpa sepengetahuan penulisnya. Dokumentasi resmi menyatakan konvensi ini dengan tegas, yaitu fungsi yang membutuhkan memori menerima parameter Allocator, dan pustaka standar mengikutinya secara konsisten. Konsekuensinya, setiap titik alokasi terlihat pada kode sumber, karena allocator harus diteruskan secara tertulis ke setiap fungsi yang membutuhkannya.
Contoh Lengkap#
Alokasi pertama dengan page_allocator#
Pustaka standar menyediakan beberapa allocator dengan strategi berbeda, dan std.heap.page_allocator adalah yang paling sederhana. Allocator ini meminta memori langsung dari sistem operasi dalam satuan halaman setiap kali diminta, tanpa lapisan daur ulang apa pun. Untuk contoh pembelajaran dan alokasi yang jarang terjadi, page_allocator sudah memadai, sedangkan program serius biasanya memilih strategi lain yang lebih hemat panggilan sistem.
Program berikut memuat pola alokasi yang lengkap, yaitu buffer 256 byte yang dialokasikan, diisi, dibaca, lalu dibebaskan.
const std = @import("std");
pub fn main() !void {
const alloc = std.heap.page_allocator;
const buffer = try alloc.alloc(u8, 256);
defer alloc.free(buffer);
const label = "sensor-ruang-utama";
@memcpy(buffer[0..label.len], label);
for (buffer[label.len..]) |*b| {
b.* = 0;
}
std.debug.print("panjang buffer: {d} byte\n", .{buffer.len});
std.debug.print("isi awal: {s}\n", .{buffer[0..label.len]});
}panjang buffer: 256 byte
isi awal: sensor-ruang-utamaBaris alloc(u8, 256) memperoleh buffer dan mengembalikan error bila memori habis, sehingga try meneruskannya keluar dari main. Label sensor disalin ke bagian awal buffer dengan @memcpy, kemudian sisa byte diisi 0 melalui capture |*b| yang menyediakan pointer ke setiap elemen. Baris defer alloc.free(buffer) menjadwalkan pembebasan pada akhir main, sehingga tidak ada jalur keluar yang melewatinya.
Argumen free adalah slice itu sendiri, bukan pointer telanjang, karena slice membawa panjang dan tipe yang diperlukan untuk mengembalikan memori dengan tepat. Setelah pembebasan berjalan, buffer tidak boleh dibaca maupun ditulis lagi. Ketentuan ini diperinci pada bagian kepasangan di bawah, beserta akibat bila pasangannya terlanggar.
# mengompilasi dan menjalankan program
zig run buffer-sensor.zigpanjang buffer: 256 byte
isi awal: sensor-ruang-utamaKonvensi parameter allocator#
Fungsi yang mengalokasikan memori menempatkan parameter alloc: std.mem.Allocator pada posisi pertama, dan nama alloc menjadi konvensi yang berlaku luas pada kode Zig. Penempatan ini membuat sifat alokatif sebuah fungsi terbaca dari tanda tangannya, tanpa perlu membaca tubuhnya. Pemanggil memilih penyedia memorinya dan meneruskannya secara tertulis, sehingga fungsi yang sama dapat bekerja dengan allocator mana pun.
// tidak-dijalankan
fn siapkan(alloc: std.mem.Allocator, panjang: usize) ![]u8 {
const buffer = try alloc.alloc(u8, panjang);
@memset(buffer, 0);
return buffer;
}
// hasilnya belum dibebaskan, sehingga nasibnya menunggu kontrakFungsi siapkan mengembalikan buffer yang belum dibebaskan, sehingga pertanyaan siapa yang wajib membebaskannya perlu jawaban yang tegas. Pertanyaan tersebut adalah inti disiplin kepemilikan, dan jawabannya dinyatakan sebagai kontrak pada dokumentasi fungsi. Untuk saat ini, cukup dicatat bahwa hasil siapkan tidak boleh dibiarkan tanpa pembebasan.
Keunggulan desain ini terasa ketika penyedia memori ditukar. Karena fungsi hanya mengenal antarmukanya, siapkan dapat menerima std.heap.page_allocator, allocator arena, maupun allocator pengujian tanpa perubahan satu baris pun di tubuhnya. Program dapat memilih strategi memori per komponen, dan pilihan itu tetap terlihat pada setiap titik panggilan.
Pasangan alloc dan free#
Setiap blok yang diperoleh dari sebuah allocator wajib dikembalikan ke allocator yang sama, tepat satu kali. Pasangan alloc dan free yang tidak lengkap atau tidak serasi menimbulkan dua kelas masalah, yaitu kebocoran bila pembebasan terlupa dan perilaku tidak terdefinisi bila memori dipakai atau dibebaskan dua kali. Zig tidak menegakkan pasangan ini pada waktu kompilasi, tetapi menyediakan pemeriksanya pada waktu pengujian.
| Alokasi | Pembebasan | Keterangan |
|---|---|---|
alloc(T, n) | free(slice) | Blok baru sepanjang n elemen |
dupe(T, data) | free(slice) | Salinan baru dari data yang ada |
dupeSentinel(T, data, 0) | free(slice) | Salinan yang berakhir dengan nilai penanda |
resize(slice, n) | tetap free(slice) | Mengubah panjang tanpa memindahkan data bila mungkin |
Tabel di atas menunjukkan bahwa free selalu menerima slice hasil alokasi dengan tipe yang sama. Panjang yang tercantum pada slice dipakai untuk menghitung ukuran yang dikembalikan, sehingga slice yang panjangnya disesuaikan melalui resize tetap dibebaskan dengan benar. Pembebasan dengan allocator yang berbeda tidak dijamin bekerja, karena setiap strategi memegang pembukuan internalnya sendiri.
Kesalahan umum#
freeterlupa pada salah satu jalur keluar fungsi. Kebiasaan yang mencegahnya adalah menulisdeferpembebasan tepat setelah alokasi berhasil, sebagaimana dilakukan padabuffer-sensor.zig.- Blok dibebaskan dengan allocator yang berbeda dari yang mengalokasikan. Pembukuan kedua allocator tidak saling mengenal, dan akibatnya tidak terdefinisi. Simpan allocator dan bloknya pada cakupan yang sama.
- Slice dipakai setelah
free. Baris pemakaiannya harus berada sebelum pembebasan, dandeferyang ditulis di awal membantu menegakkan urutan itu.
Ringkasan#
std.mem.Allocatoradalah antarmuka penyedia memori dengan metodealloc,free,dupe,dupeSentinel, danresize.allocmengembalikan error union yang membawaerror.OutOfMemory, sehingga kegagalan alokasi ditangani secara eksplisit.- Fungsi yang mengalokasikan menerima parameter
Allocatorpada posisi pertama sesuai konvensi Zig. - Setiap
allocberpasangan dengan tepat satufreepada allocator yang sama, dan memori yang sudah dibebaskan tidak boleh disentuh lagi.
Referensi#
- Memory — Zig Language Reference — Latar model alokasi memori Zig dan konvensi parameternya.
- std.mem.Allocator — Dokumentasi pustaka standar Zig — Metode antarmuka allocator beserta kontrak alokasi dan pembebasannya.