Latihan membuat instruksi pembelajaran mengenai operasi memory saat menuliskan kode pemrograman, dengan implementasi bahasa pemrograman Rust.
Tujuan pembelajaran
Setelah selesai modul ini, peserta akan mampu:
Menjelaskan perbedaan stack dan heap, serta implikasinya (ukuran, lifetime, alokasi).
Menjelaskan apa itu pointer (alamat), dan perbedaan antara pointer C (
*) dan referensi Rust (&,&mut) secara konseptual.Mengidentifikasi dan menjelaskan kesalahan memori umum: uninitialized reads, use-after-free, double free, buffer overflow.
Menunjukkan bagaimana Rust (ownership + borrow checker) mencegah kelas kesalahan tertentu, dan kapan perlu menggunakan
unsafe.Mengonversi pola idiomatik C (mis. string copy, swap by pointer) ke idiom Rust yang aman.
Menggunakan alat bantu debugging memori di kedua ekosistem: Valgrind (C) dan Miri / sanitizers (Rust).
Prasyarat
Pengetahuan dasar C (variabel, pointer dasar) membantu karena contoh CS50 berbasis C.
Dasar Rust: variabel,
let,mut,struct,fn,String,Vec,Box, referensi (&), dan traitCopy/Clone.Terminal/basic tooling:
cargo,rustc,miri(opsional),valgrind(opsional).
Inti konsep: Stack vs Heap
Stack
Lokasi penyimpanan data untuk variabel lokal dengan lifetime yang jelas (scope).
Alokasi/dealokasi sangat cepat (LIFO).
Ukuran terbatas (stack overflow jika menaruh array besar di stack).
Contoh C:
int n = 50;—nberada di stack. (lihat slide dan contoh addresses).
Heap
Lokasi untuk data yang dialokasikan dinamis (runtime), mis.
mallocdi C,Box/Vec/Stringdi Rust.Alokasi/ dealokasi lebih lambat, memerlukan manajemen (free / drop).
Data heap hidup sampai di-free (atau sampai owner di Rust hilang).
Contoh C:
int *p = malloc(sizeof(int));→*pberada di heap. (lihat contoh memory.c / copyX.c).
Garis besar perbandingan
Stack: cepat, automatic, lifetime terikat scope.
Heap: fleksibel ukuran dan lifetime, memerlukan manajemen (manual di C; RAII/ownership di Rust).
Pointer & alamat: pemahaman intuitif
Pointer = alamat memori. Menyimpan alamat sebuah nilai. Dalam C:
int *p = &n;. Anda bisa dereference pointer (*p) untuk membaca/menulis nilai pada alamat tersebut. Contoh addresses1.c, addresses2.c.Pointer arithmetic di C:
*(s + 1)untuk mengaksess[1]. Ini raw dan kuat, sekaligus sumber banyak bug (out-of-bounds). Slide CS50 memperlihatkan string bytes dan alamatnya jelas.
Rust mapping (konseptual):
Rust menyediakan referensi
&T(immutable) dan&mut T(mutable) yang mirip pointer tetapi tidak memberi arithmetic. Referensi aman: tidak mungkin menjadi dangling atau double free semudah di C karena borrow checker.Untuk akses pointer-style/low-level ada raw pointers
*const T/*mut Tdanunsafe— tapi pemula tidak perlu memulai dari sini.
Kesalahan memori klasik (dengan contoh dari CS50) dan cara Rust mencegahnya
1. Uninitialized reads / garbage values
C example: reading from stack array tanpa inisialisasi (garbage.c).
C bisa membaca memory yang belum diinisialisasi → hasil tak terduga.
Rust menolak akses ke variabel yang belum diinisialisasi; compiler menegakkan inisialisasi sebelum dipakai.
2. Use-after-free / dangling pointer
C pattern: free() lalu masih menggunakan pointer → crash/UB. Banyak contoh di lecture slide yang menjelaskan malloc/free.
Rust ownership model: ketika owner (mis.
Box<T>,Vec<T>) keluar scope,dropdipanggil; borrow checker memastikan tidak ada referensi hidup yang bisa mengakses memory setelah owner drop. Jadi use-after-free umum di C tidak muncul di Rust kecuali memakaiunsafe.
3. Double free
C: memanggil free() dua kali pada pointer sama → UB. Contoh copy5.c atau memory.c yang menunjukkan free handling.
Rust: ownership tunggal mencegah double free karena ownership pindah (move) atau borrow rules mencegah multi-owner. Untuk shared ownership gunakan
Rc/Arcyang menghitung refcount dan mengelola drop otomatis — tapi berhati-hati terhadap reference cycles.
4. Buffer overflow / out-of-bounds
C: scanf atau strcpy dapat menulis melewati batas (slides & get2.c/get3.c).
Rust: indexing
v[i]panics on OOB in safe Rust; preferget()returningOption.String/VecAPI membuat buffer overflow accidental lebih sulit. Namun diunsafeatau konversi dari FFI, programmer harus hati-hati.
Alat bantu debugging memory
C world: Valgrind (memcheck) — memeriksa uninitialized reads, leaks, invalid frees. Slides menunjukkan valgrind dan contoh.
Rust world:
Miri — interpreter untuk mendeteksi undefined behaviour (use of uninitialized memory, invalid borrows) pada kode Rust.
AddressSanitizer (ASan) via compiler flags for detecting OOB, use-after-free when building with
-Z sanitizer(nightly) or using clang sanitizers for C components.cargo clippy / static analyzers membantu menemukan pola bug.
Praktik: gunakan Miri (
cargo miri test) untuk mengecek UB di kode Rust; di C gunakan Valgrind pada contoh copy5.c / memory.c.
Peta konsep: bagaimana pola C di CS50 diterjemahkan ke Rust
Di bagian source CS50 ada contoh-contoh yang sering dipakai: swap (by value vs by pointer), copyX.c (copy string with malloc and free), addresses* (pointer and addresses), get/scanf pitfalls. Saya ringkas pola dan bagaimana mengonversinya ke idiom Rust.
Swap two ints
C (pass by value): tidak menukar:
void swap(int a, int b) { int tmp = a; a = b; b = tmp; }C (by pointer): benar:
void swap(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; }Rust idiom:
Use mutable references:
fn swap(a: &mut i32, b: &mut i32) {
let tmp = *a;
*a = *b;
*b = tmp;
}
fn main() {
let mut x = 1;
let mut y = 2;
swap(&mut x, &mut y);
}Clue: Rust references behave like pointers but checked by compiler — no raw pointer arithmetic.
String copy with allocation
C manual (copy4.c): allocate t = malloc(strlen(s)+1), strcpy, then free(t). Rust idiom (safe, heap-allocated string copy):
fn main() {
let s = String::from("hello");
let t = s.clone(); // deep copy of heap string
// or if you need new allocation explicitly:
let t = String::from(&s);
println!("s: {}, t: {}", s, t);
}Clue: String owns heap memory; clone() allocates new heap buffer and copies bytes.
Uninitialized array / garbage reads
C int scores[1024]; printing values yields garbage (memory.c). Rust disallows using uninitialized data. Use default initialization:
let scores: [i32; 1024] = [0; 1024];Or heap allocate via Vec:
let mut scores = vec![0i32; 1024];Use-after-free demonstration -> Rust prevention
C misuse: keep pointer after free(). Rust pattern: owner drop occurs when variable goes out of scope; borrow checker prevents referencing after owner moved. To show use-after-free in Rust you'd need unsafe and raw pointers — avoid for beginners.
Contoh latihan terstruktur (praktik & exercises)
Berikut beberapa latihan (dari mudah ke menantang). Untuk tiap latihan saya sertakan clue singkat untuk membantu.
Latihan 1: alamat & referensi sederhana
Tujuan: lihat alamat variabel dan perbedaan stack vs heap.
Buat program Rust yang:
Membuat
let n = 50;dan mencetak alamat&n(println!("{:p}", &n);).Membuat
let b = Box::new(50);dan mencetak alamat&*b(alamat heap) (println!("{:p}", &*b);). Clue:Box<T>menyimpan data di heap;&*bderef box ke T lalu ambil alamatnya.
Latihan 2: swap (konversi dari swap1.c)
Tujuan: praktek mutable references.
Implementasikan fungsi
swap(a: &mut i32, b: &mut i32)lalu panggilnya untuk menukar dua variabel. Clue: deref referensi untuk membaca/menulis*a,*b.
Latihan 3: salin string (konversi dari copy4.c)
Tujuan: praktik String, clone().
Baca string dari stdin (pakai
read_line), buat salinan (clone), ubah huruf pertama dari salinan menjadi uppercase, lalu cetak kedua string. Clue: gunakans.trim_end().to_string()untuk membersihkan newline sebelum clone; gunakanchars()/replace_range()untuk modifikasi.
Latihan 4: mendeteksi OOB vs safe get
Tujuan: aman akses koleksi.
Buat
Vec<i32>dengan 3 elemen. Coba aksesv[5]vsv.get(5)dan tanganiOption. Clue:v[5]panic (runtime),v.get(5)returnNone.
Latihan 5: menerjemahkan contoh CS50 copy5.c ke Rust (deteksi leak)
Tujuan: periksa perilaku alokasi/leak.
C:
int *x = malloc(3*sizeof(int)); x[1]=72; x[2]=73; x[3]=33;→ out-of-bounds write.Tugas: buat contoh Rust yang menyerupai kesalahan (harus pakai unsafe raw pointer), hanya sebagai demonstrasi; jangan gunakan unsafe di aplikasi nyata. Clue: gunakan
Vec::with_capacity(3)laluunsafeblock untuk menulis di luar bounds (HARUS dilakukan hanya untuk eksperimen di lingkungan terkendali), atau gunakan Valgrind / Miri untuk mendeteksi.
Catatan pedagogis: latihan 5 adalah opsional dan harus diawasi; tujuannya melihat alat pendeteksi kesalahan (Miri/Valgrind).
Jawaban singkat / clue untuk beberapa latihan
Latihan 1:
println!("stack addr: {:p}", &n); println!("heap addr: {:p}", &*b);Latihan 2:
fn swap(a: &mut i32, b: &mut i32) { let tmp = *a; *a = *b; *b = tmp; }Latihan 3: baca
let mut s = String::new(); std::io::stdin().read_line(&mut s).unwrap(); let mut t = s.trim_end().to_string(); t.replace_range(0..1, &t[0..1].to_uppercase());Latihan 4:
match v.get(5) { Some(x) => println!("{}", x), None => println!("oob") }
Edge cases (perlu diberi perhatian dalam pembelajaran)
Stack overflow: membuat array sangat besar di stack (
let big: [u8; 10_000_000]) akan crash; gunakanVecatauBox<[T]>. (CS50 meliput stack overflow / buffer overflow risks).Panic vs UB: Rust safe code panics on
v[index]OOB; C may silently corrupt memory and continue. Perbedaan ini harus dipahami.Uninitialized reads are UB: membaca memory tanpa initialize (C) versus compile-time enforced initialization (Rust).
Ownership transfer pitfalls: memindahkan owner (
let b = a;) membuatainvalid in Rust; di C, copy pointer tidak transfer ownership sehingga double-free possible.FFI boundaries: saat berinteraksi dengan C via FFI, Rust safety guarantees can be broken — treat FFI carefully and validate inputs.
Tools & workflow yang direkomendasikan
Untuk C examples: compile with
-gand run Valgrind:gcc -g file.c && valgrind ./a.out(Valgrind shows leaks, invalid reads/writes).For Rust: use
cargo miri test(Miri) to detect undefined behaviour such as uninitialized reads. Usecargo +nightly run -Z sanitizer=addressfor ASan if available.Use sanitizers and static linters early (Clippy, compiler warnings) to catch patterns.
Refleksi & pertanyaan pemicu diskusi (dengan clue)
Mengapa use-after-free lebih mudah terjadi di C daripada di Rust? Clue: C memisahkan pointer dari ownership; Rust mengikat ownership ke variable dan memaksa pemeriksaan pada compile time.
Apa trade-off antara kontrol penuh (C) dan safety-by-default (Rust)? Clue: C memberi kebebasan (dan risiko) untuk performance tuning; Rust memberi safety yang kuat namun masih bisa
unsafebila perlu.Kapan Anda tetap perlu
unsafedi Rust? Clue: interoperabilitas FFI, optimasi low-level, atau interfacing dengan hardware; gunakanunsafehanya untuk batas wilayah kecil dan teruji.Bagaimana alat seperti Valgrind dan Miri membantu proses belajar? Clue: mereka mengungkap kelas bug yang sulit dideteksi manual, dan membantu memahami implikasi memory misuse.
Ringkasan praktis (cheat-sheet)
Stack= local variables, limited size.Heap= dynamic allocation (malloc/Box/Vec), flexible size.Pointerdi C = alamat (raw) → arithmetic allowed → risk.&T/&mut Tdi Rust = safe references → no pointer arithmetic.Use
Vecfor dynamic-sized collections; useBoxfor single heap values or recursive types.Tools: Valgrind (C), Miri / sanitizers (Rust), Clippy for linting.
Referensi
Harvard University. CS50 — Week 4: Memory (lecture slides). Available: https://cs50.harvard.edu/x/weeks/4/ . (Also: available lecture PDF).
Harvard University. CS50 — Week 4 source code examples (addresses.c, copy*.c, memory.c, swap*.c, etc.). (Also available at the CS50 week 4).
The Rust Project. The Rust Programming Language (The Book) — chapters on Ownership and on References. URL: https://doc.rust-lang.org/book/
The Rust Project. Miri — an interpreter for Rust’s mid-level intermediate representation, for detecting undefined behavior in Rust code. URL: https://github.com/rust-lang/miri


