make dan free

Disiplin alokasi dan pelepasan memori secara eksplisit.

Daftar isi

Proc bawaan make menyediakan memori dari alokator context, dan proc delete serta free melepaskannya kembali. Tidak ada pengumpul sampah di Odin, sehingga kedua sisi pasangan itu ditulis secara eksplisit oleh pengembang. Topik ini membahas cara make bekerja pada slice, dynamic array, yakni array yang panjangnya dapat bertumbuh, dan map, cara membaca nilai error yang menyertai make, serta disiplin memasangkan setiap alokasi dengan pelepasannya. Setelah topik ini, pola make dan delete yang dipasangkan dengan defer dapat diterapkan pada program apa pun tanpa menyisakan memori yang tidak pernah dilepaskan.

make menyediakan memori melalui context#

make menerima tipe dan panjang, lalu meminta memori dari context.allocator tanpa argumen tambahan. Slice memakai bentuk make([]int, 4), dynamic array dapat diberi kapasitas awal dengan make([dynamic]int, 0, 8), dan map dibuat dengan make(map[string]int). Memori hasil make diisi nol, sehingga elemen []int yang baru dibuat bernilai 0 dan map baru siap dipakai tanpa persiapan lain. Karena alokasi dapat gagal, make menawarkan nilai kedua bertipe Allocator_Error yang diperiksa dengan bentuk nilai, err := make(...) diikuti uji if err != nil. Nilai errornya berasal dari enum Allocator_Error pada paket base:runtime dengan anggota seperti .Out_Of_Memory dan .Invalid_Argument. Proc pada paket core:mem menandai dirinya dengan @(require_results), yang memaksa hasil errornya diperiksa oleh compiler, sedangkan bentuk make menyerahkan keputusan itu kepada penulisnya.

Di luar kontainer, new(T) menyediakan satu nilai bertipe T dan mengembalikan pointernya dari alokator context, sehingga pembuatan satu struct hasil pengukuran pun tidak memerlukan argumen alokator. Sebaliknya, deklarasi array berukuran tetap seperti [4]int tidak memanggil alokator sama sekali karena seluruh isinya tinggal di variabel itu sendiri. Keputusan memakai make atau new berarti memindahkan data ke memori yang dikelola alokator, dan keputusan itulah yang menetapkan kewajiban pelepasannya kelak.

delete, free, dan free_all#

Pelepasan memori memakai tiga proc yang sasarannya berbeda. Proc bawaan delete menerima nilai hasil make, yaitu slice, dynamic array, string yang dialokasikan, atau map, dan mengembalikan Allocator_Error sebagai laporan pelepasannya. Proc free pada paket core:mem menerima pointer mentah dan jarang ditulis langsung karena delete sudah membungkusnya. Proc free_all mengosongkan seluruh isi satu alokator sekaligus, tetapi hanya alokator yang mendukung mode Free_All yang melakukannya. Tabel berikut merangkum ketiganya beserta perilaku yang sudah diverifikasi pada alokator default heap.

ProcSasaranPerilaku pada alokator heap default
delete(nilai)Slice, dynamic array, map, string alokasiMelepaskan bloknya dan mengembalikan None
free(ptr)Pointer mentah dari allocMelepaskan blok yang dimiliki. free(nil) mengembalikan None
free_all(context.allocator)Seluruh isi satu alokatorMengembalikan Mode_Not_Implemented karena heap default tidak mendukungnya

Pelepasan dua kali pada pointer yang sama tidak dilaporkan sebagai error, melainkan jatuh ke perilaku tak terdefinisi.

resize dan pertumbuhan dynamic array#

Dynamic array bertumbuh melalui append, yang meminta memori baru dari context.allocator setiap kali kapasitas terlampaui dan menyalin isi lama ke blok baru. Proc bawaan resize menaikkan panjang array secara langsung dengan bentuk resize(&arr, n) dan mengembalikan Allocator_Error, sedangkan elemen baru hasil resize diisi nol. Resize juga dipakai oleh pustaka di dalam append ketika alokasi ulang diperlukan, sehingga dua cara itu berbagi mekanisme yang sama. Panjang dan kapasitas dapat dibaca dengan len dan cap untuk mengamati kapan alokasi ulang terjadi, yaitu ketika cap meloncat ke nilai yang lebih besar. Pada alokator yang tidak mendukung mode Resize, errornya adalah Mode_Not_Implemented, dan pola penanganannya sama dengan penanganan error alokasi.

Keluaran program pada bagian berikutnya memperlihatkan perilaku penting pertumbuhan itu. resize(&nilai, 12) menaikkan panjang dari 10 menjadi 12 tanpa membuang sepuluh nilai yang sudah ada, dan dua slot baru berisi 0 sampai diisi secara eksplisit. Pasangan len dan cap membantu menentukan kapan pertumbuhan memicu alokasi ulang, yaitu ketika cap meloncat melampaui nilai sebelumnya, sementara len hanya mengikuti permintaan program.

Disiplin: setiap make memiliki pasangan delete#

Inti disiplinnya satu kalimat: setiap make memiliki delete yang ditulis berdekatan dengannya. Bentuk paling tahan salah adalah menempelkan defer delete(x) tepat setelah alokasi, sehingga pelepasan terjadi otomatis ketika proc selesai, termasuk pada jalur return di tengah proses. Untuk nilai yang berumur lebih panjang dari satu proc, kepemilikannya diserahkan secara jelas, misalnya melalui field struct yang memiliki proc pelepasan sendiri. Program berikut menerapkan disiplin itu pada pemrosesan nilai ujian: dynamic array dan map dialokasikan, diisi, lalu dilepaskan melalui defer, dengan resize di tengah proses untuk menaikkan panjangnya.

odin main.odin
package main

import "core:fmt"

proses_nilai :: proc() {
        // make mengembalikan Allocator_Error sebagai nilai kedua
        nilai, err := make([dynamic]int, 0, 8)
        if err != nil {
                fmt.println("alokasi dynamic array gagal:", err)
                return
        }
        // pasangan make dan delete disandingkan lewat defer
        defer delete(nilai)

        masukan := [10]int{72, 85, 90, 68, 77, 94, 59, 88, 81, 76}
        for n in masukan {
                // pertumbuhan melampaui kapasitas awal memicu alokasi baru dari context
                append(&nilai, n)
        }

        // resize menaikkan panjang tanpa membuang isi yang sudah ada
        gagal := resize(&nilai, 12)
        fmt.println("error resize:", gagal)
        nilai[10], nilai[11] = 83, 79

        fmt.println("jumlah nilai:", len(nilai))

        // map juga memakai allocator dari context
        ringkasan := make(map[string]int)
        defer delete(ringkasan)
        for n in nilai {
                if n >= 75 {
                        ringkasan["lulus"] += 1
                } else {
                        ringkasan["remedial"] += 1
                }
        }
        fmt.println("lulus:", ringkasan["lulus"], "remedial:", ringkasan["remedial"])
}

main :: proc() {
        proses_nilai()
        fmt.println("proses selesai, seluruh alokasi sudah dilepaskan")
}
bash
# sumberkan lingkungan sandbox jalur odin bila belum tersedia
[ -f /home/kal/my-project/tools/env.sh ] && source /home/kal/my-project/tools/env.sh
# jalankan program dari dalam folder topik
odin run main.odin -file
text Keluaran
error resize: None
jumlah nilai: 12
lulus: 9 remedial: 3
proses selesai, seluruh alokasi sudah dilepaskan

Jumlah lulus dan remedial dihitung dari dua elemen tambahan hasil resize beserta sepuluh nilai masukan, dan dua defer menjamin kedua kontainer dilepaskan pada akhir proc. Program yang mengikuti disiplin ini selalu berakhir dengan alokasi nol, dan topik tentang pelacakan kebocoran menyediakan alat pembuktiannya.

Kesalahan umum#

Kesalahan pertama adalah memanggil resize untuk slice biasa. Proc resize menyasar dynamic array dan memori mentah, sehingga argumen bertipe pointer ke []int ditolak compiler dengan pesan ketidakcocokan tipe. Slice tidak bisa bertumbuh karena panjangnya ditetapkan pada saat pembuatan, dan pertumbuhan hanya berlaku pada [dynamic]T.

Kesalahan kedua adalah memakai nilai kembalian make tanpa memeriksa errornya. Pada kegagalan, nilai pertamanya adalah slice kosong, dan pengindeksannya memicu error batas saat program berjalan, bukan saat kompilasi. Uji if err != nil ditempatkan sebelum pemakaian pertama, dan jalur kegagalan cukup melaporkan error lalu keluar dari proc.

Kesalahan ketiga adalah menaruh defer delete jauh dari make-nya, misalnya di awal proc sementara alokasinya berada di tengah percabangan. Jarak itu menimbulkan jalur yang melepaskan nilai yang belum dialokasikan dan jalur yang mengalokasikan lebih dari satu nilai tetapi hanya melepaskan satu. Penempatan berdekatan pada titik alokasi menghilangkan kedua jalur tersebut sekaligus.

Ringkasan#

  • make mengalokasikan memori berisi nol dari context.allocator dan menawarkan nilai kedua bertipe Allocator_Error.
  • delete melepaskan slice, dynamic array, map, dan string alokasi dengan laporan error, sedangkan free menyasar pointer mentah dan free_all mengosongkan satu alokator sekaligus.
  • resize(&arr, n) menaikkan panjang dynamic array dengan elemen baru berisi nol dan mengembalikan Allocator_Error.
  • Disiplin yang tahan salah: tulis defer delete(x) tepat setelah make, karena pelepasan ganda berujung crash.

Referensi#