C tidak memiliki sistem ownership bawaan seperti Rust, tetapi konsep ini tetap berlaku secara konvensi. Setiap blok heap yang dialokasikan oleh malloc/calloc/realloc harus dibebaskan tepat satu kali oleh pemilik yang jelas. Tanpa konvensi ini, kita mudah terjebak dalam dua bug: double free (dua fungsi mengira dirinya pemilik) atau memory leak (tidak ada fungsi yang mengira dirinya pemilik). Pada chapter ini kita akan memetakan pola ownership yang umum di C—single owner, transfer ownership, dan borrowing—dan melihat bagaimana konvensi ini menyelamatkan kode dari bug memori.
Tiga Pola Ownership di C #
Konvensi yang umum dipakai di pustaka C:
- Single owner — satu fungsi (biasanya yang memanggil
malloc) adalah pemilik tunggal dan satu-satunya yang boleh memanggilfree. Fungsi lain menerima pointer sebagai “peminjam” (borrowing). - Transfer ownership — fungsi mengalokasikan blok lalu mengembalikan pointer; pemanggil menjadi pemilik baru dan wajib memanggil
free. Pola ini umum pada “factory function”. - Container ownership — sebuah struct (misalnya
Vector) memegang pointer; fungsivector_destroy(&v)adalah satu-satunya yang boleh memanggilfreepada pointer tersebut.
Mekanisme kerja di balik layar: standar C tidak melacak siapa pemilik sebuah blok. Allocator glibc hanya tahu alamat dan ukuran blok. Konsep ownership sepenuhnya bersifat dokumentasi dan konvensi antar programmer—compiler tidak bisa memverifikasinya (kecuali dengan tool eksternal seperti Valgrind atau Clang Static Analyzer). Inilah sebabnya konvensi yang konsisten sangat penting: komentar, penamaan fungsi, dan urutan deklarasi harus jelas siapa pemilik.
Pola Single Owner #
Fungsi buat_buffer mengalokasikan dan mengembalikan pointer; main adalah pemilik dan satu-satunya yang memanggil free. Fungsi pakai_buffer hanya meminjam—bukan pemilik.
#include <stdio.h>
#include <stdlib.h>
/* Pemilik tunggal: fungsi yang mengalokasikan bertanggung jawab membebaskan */
int *buat_buffer(size_t n) {
int *buf = malloc(n * sizeof(int));
if (buf == NULL) return NULL;
for (size_t i = 0; i < n; i++) buf[i] = (int)i * 5;
return buf;
}
void pakai_buffer(const int *buf, size_t n) {
/* PEMBERITAHUAN: buf hanya dipakai, TIDAK boleh di-free */
if (buf == NULL) return;
printf("Isi buffer: ");
for (size_t i = 0; i < n; i++) printf("%d ", buf[i]);
printf("\n");
}
int main(void) {
int *b = buat_buffer(4);
if (b == NULL) return 1;
pakai_buffer(b, 4);
/* main adalah pemilik -> main yang memanggil free */
free(b);
b = NULL;
printf("Buffer dibebaskan, b = %p\n", (void*)b);
return 0;
}Output yang Dihasilkan:
Isi buffer: 0 5 10 15
Buffer dibebaskan, b = (nil)Perhatikan beberapa isyarat kontrak ownership dalam kode:
buat_buffermengalokasikanmallocdan mengembalikan pointer—ini adalah factory function. Pemanggil (main) menjadi pemilik baru.pakai_buffermenerimaconst int *buf—keywordconstmenandakan bahwa fungsi ini tidak akan memodifikasi data, dan secara konvensi tidak akan memanggilfree.mainadalah satu-satunya yang memanggilfree(b).
Mari kita telaah baris-baris kode di atas:
- Baris 5 (
int *buat_buffer(size_t n)): nama fungsibuat_*mengikuti konvensi “factory”—fungsi yang mengembalikan sumber daya baru yang harus dikelola pemanggil. - Baris 12 (
void pakai_buffer(const int *buf, size_t n)): parameterconst int *menyatakan “pointer ke data konstan”—peminjam tidak boleh menulis melalui pointer ini, apalagi memanggilfree. Ini adalah kontrak yang ditegakkan compiler. - Baris 22 (
int *b = buat_buffer(4);):mainmenerima ownership. Setelah baris ini,mainbertanggung jawab atas blok yang ditunjukb. - Baris 26 (
pakai_buffer(b, 4);):mainmeminjamkanbkepakai_buffer. Fungsi itu tidak mengubah kepemilikan—setelah fungsi kembali,mainmasih pemilik. - Baris 29 (
free(b);):mainmengembalikan blok ke allocator. Inilah satu-satunyafreeuntuk blok ini. - Baris 30 (
b = NULL;): menghapus dangling pointer—praktik wajib setelahfree.
Pola Transfer Ownership #
Kadang fungsi mengembalikan pointer ke blok yang dialokasikan, dan pemanggil menjadi pemilik baru. Komentar eksplisit sangat membantu di sini—pemanggil tidak punya cara lain untuk tahu bahwa ia bertanggung jawab memanggil free.
#include <stdio.h>
#include <stdlib.h>
/* Transfer ownership: pemanggil menjadi pemilik baru dan wajib free */
int *ambil_kepemilikan(void) {
int *data = malloc(3 * sizeof(int));
if (data == NULL) return NULL;
data[0] = 100; data[1] = 200; data[2] = 300;
/* Setelah return, pemanggil bertanggung jawab membebaskan data */
return data;
}
void simpan_ke_log(int *data, size_t n) {
/* Borrowing: hanya memakai, tidak free */
printf("Log: ");
for (size_t i = 0; i < n; i++) printf("%d ", data[i]);
printf("\n");
}
int main(void) {
/* main menerima kepemilikan dari ambil_kepemilikan */
int *mine = ambil_kepemilikan();
if (mine == NULL) return 1;
simpan_ke_log(mine, 3); /* meminjam tanpa transfer */
free(mine);
mine = NULL;
printf("mine dibebaskan\n");
return 0;
}Output yang Dihasilkan:
Log: 100 200 300
mine dibebaskanKontrak ownership ambil_kepemilikan harus didokumentasikan dengan komentar eksplisit: “pemanggil menjadi pemilik baru dan wajib free”. Tanpa dokumentasi ini, pemanggil mungkin mengira fungsi tersebut memelihara cache internal—dan tidak pernah memanggil free, menyebabkan memory leak.
Beberapa pustaka memakai suffix nama fungsi sebagai penanda ownership: strdup (mengalokasikan dan mengembalikan—pemanggil wajib free), asprintf (sama), getline (sama). Pola penamaan konsisten ini membantu programmer mengenali kontrak tanpa membaca dokumentasi panjang.