1. Pengenalan Memory Management
Memory management adalah salah satu aspek paling penting dan menantang dalam pemrograman C++. Berbeda dengan bahasa seperti Python atau Java yang memiliki garbage collector, C++ memberikan kontrol penuh kepada programmer atas pengelolaan memori.
Kontrol penuh ini memberikan keuntungan performa yang luar biasa, tetapi juga mengharuskan programmer bertanggung jawab penuh atas alokasi dan dealokasi memori. Kesalahan dalam memory management bisa menyebabkan memory leaks, dangling pointers, buffer overflow, dan bahkan security vulnerabilities.
Mengapa Memory Management Penting?
| Aspek | Penjelasan |
|---|---|
| Performa | Alokasi/dealokasi yang tepat menghasilkan program yang cepat dan efisien |
| Stabilitas | Memory leaks menyebabkan program menggunakan RAM terus meningkat hingga crash |
| Keamanan | Buffer overflow adalah celah keamanan yang paling banyak dieksploitasi |
| Skalabilitas | Server yang berjalan 24/7 harus bebas memory leak agar tidak perlu restart |
┌──────────────────────────────────────────────────────────┐ │ LAYOUT MEMORI PROGRAM C++ │ │ │ │ ┌──────────────────────┐ Alamat Tinggi (0xFFFF...) │ │ │ STACK │ ← Lokal variabel, function │ │ │ ↓ (tumbuh ke bawah) │ frames, return addresses │ │ │ │ │ │ │ │ │ │ │ │ ▼ ▼ ▼ ▼ ▼ │ Biasanya 1-8 MB │ │ ├──────────────────────┤ │ │ │ (Free Space) │ │ │ ├──────────────────────┤ │ │ │ ▲ ▲ ▲ ▲ ▲ │ │ │ │ │ │ │ │ │ │ │ │ │ ↑ (tumbuh ke atas) │ ← new/malloc, dynamically │ │ │ HEAP │ allocated objects │ │ │ │ Biasanya GB tersedia │ │ ├──────────────────────┤ │ │ │ BSS (uninit.) │ ← Global variabel tanpa init │ │ ├──────────────────────┤ │ │ │ DATA (init.) │ ← Global variabel dengan init │ │ ├──────────────────────┤ │ │ │ TEXT (code) │ ← Instruksi program (read-only)│ │ └──────────────────────┘ Alamat Rendah (0x0000...) │ └──────────────────────────────────────────────────────────┘
2. Model Memori C++
C++ menggunakan model memori yang terbagi menjadi beberapa area: text segment (kode), data segment (global/static), stack (lokal), dan heap (dinamis). Setiap area memiliki karakteristik dan aturan lifetime yang berbeda.
Variabel Global dan Static
#include <iostream>
using namespace std;
// Global variable — hidup sepanjang program, di data segment
int globalCounter = 0;
const double PI = 3.14159265358979; // Read-only (di text segment atau rodata)
void fungsi() {
// Static local — hidup sepanjang program, tapi hanya terlihat di fungsi ini
static int callCount = 0; // Inisialisasi HANYA sekali
callCount++;
cout << "Fungsi dipanggil " << callCount << " kali" << endl;
}
class Singleton {
private:
static Singleton* instance;
string data;
Singleton() : data("Singleton Instance") {
cout << "Singleton created" << endl;
}
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
string getData() const { return data; }
// Note: destructor tidak dipanggil otomatis untuk static
// Perlu cleanup manual atau at_exit handler
};
Singleton* Singleton::instance = nullptr;
int main() {
// Static local variable
fungsi(); // "Dipanggil 1 kali"
fungsi(); // "Dipanggil 2 kali"
fungsi(); // "Dipanggil 3 kali"
// Global counter
globalCounter = 42;
cout << "Global: " << globalCounter << endl;
// Static member
auto* s = Singleton::getInstance();
cout << s->getData() << endl;
// sizeof — cek ukuran
cout << "sizeof(int): " << sizeof(int) << " bytes" << endl;
cout << "sizeof(double): " << sizeof(double) << " bytes" << endl;
cout << "sizeof(pointer): " << sizeof(void*) << " bytes" << endl;
return 0;
}
3. Stack vs Heap
Memahami perbedaan antara stack dan heap adalah fundamental untuk memory management di C++.
Perbandingan Stack dan Heap
| Aspek | Stack | Heap |
|---|---|---|
| Kecepatan | 🟢 Sangat cepat (push/pop) | 🟡 Lebih lambat (alloc/dealloc) |
| Ukuran | 🔴 Terbatas (1-8 MB) | 🟢 Besar (GB tersedia) |
| Allocation | Otomatis (compiler) | Manual (new/delete) |
| Lifetime | Otomatis (scope-based) | Sampai di-delete manual |
| Fragmentasi | Tidak ada | Bisa terjadi |
| Thread Safety | Per-thread punya stack | Shared, perlu sinkronisasi |
| Error | Stack overflow | Memory leak, dangling ptr |
#include <iostream>
#include <string>
using namespace std;
class Objek {
string nama;
public:
Objek(const string& n) : nama(n) {
cout << "[+] Objek \"" << nama << "\" dibuat" << endl;
}
~Objek() {
cout << "[-] Objek \"" << nama << "\" dihapus" << endl;
}
void tampil() const { cout << "Objek: " << nama << endl; }
};
int main() {
cout << "=== STACK ALLOCATION ===" << endl;
{
// Objek di stack — otomatis dihapus saat keluar scope
Objek stackObj("Stack-Object");
stackObj.tampil();
// Destructor dipanggil otomatis di akhir scope
} // stackObj dihapus di sini
cout << "\n=== HEAP ALLOCATION ===" << endl;
{
// Objek di heap — harus dihapus manual
Objek* heapObj = new Objek("Heap-Object");
heapObj->tampil();
// Jangan lupa delete!
delete heapObj; // Destructor dipanggil di sini
heapObj = nullptr;
}
cout << "\n=== ARRAY STACK ===" << endl;
{
// Array di stack — ukuran harus compile-time constant
int arr[5] = {1, 2, 3, 4, 5};
for (int i = 0; i < 5; i++) cout << arr[i] << " ";
cout << endl;
}
cout << "\n=== ARRAY HEAP ===" << endl;
{
// Array di heap — ukuran bisa runtime
int n = 5;
int* arr = new int[n];
for (int i = 0; i < n; i++) arr[i] = (i + 1) * 10;
for (int i = 0; i < n; i++) cout << arr[i] << " ";
cout << endl;
delete[] arr; // Gunakan delete[] untuk array!
}
cout << "\n=== VLA ALTERNATIVE (stack) ===" << endl;
{
// C++ tidak punya VLA resmi, tapi gunakan heap atau std::vector
int n = 5;
int* arr = new int[n](); // () menginisialisasi ke 0
for (int i = 0; i < n; i++) cout << arr[i] << " ";
cout << endl;
delete[] arr;
}
return 0;
}
Stack size terbatas (biasanya 1-8 MB). Deklarasi array sangat besar di stack atau rekursi terlalu dalam bisa menyebabkan stack overflow. Gunakan heap atau std::vector untuk data besar.
4. New dan Delete
new dan delete adalah operator C++ untuk alokasi dan dealokasi heap memory. Ini adalah pengganti dari malloc()/free() di C dengan tambahan constructor/destructor call.
#include <iostream>
#include <string>
using namespace std;
class Kendaraan {
string merek;
int roda;
public:
Kendaraan(const string& m, int r) : merek(m), roda(r) {
cout << "Kendaraan " << merek << " dibuat (constructor)" << endl;
}
~Kendaraan() {
cout << "Kendaraan " << merek << " dihapus (destructor)" << endl;
}
void info() const {
cout << merek << " (" << roda << " roda)" << endl;
}
};
int main() {
// Single object allocation
cout << "--- Single Object ---" << endl;
Kendaraan* mobil = new Kendaraan("Toyota", 4);
mobil->info();
delete mobil; // Wajib! Jika tidak → memory leak
mobil = nullptr; // Good practice
// Array allocation
cout << "\n--- Array ---" << endl;
Kendaraan* garasi = new Kendaraan[3]{
{"Honda", 4}, {"Yamaha", 2}, {"BMW", 4}
};
for (int i = 0; i < 3; i++) {
garasi[i].info();
}
delete[] garasi; // Array: gunakan delete[]!
garasi = nullptr;
// Placement new — construct di alamat yang sudah dialokasikan
cout << "\n--- Placement New ---" << endl;
char buffer[sizeof(Kendaraan)];
Kendaraan* ptr = new (buffer) Kendaraan("Custom", 3);
ptr->info();
ptr->~Kendaraan(); // Panggil destructor manual!
// Nothrow new — return nullptr jika gagal (bukan exception)
cout << "\n--- Nothrow New ---" << endl;
Kendaraan* safe = new (nothrow) Kendaraan("Safe", 4);
if (safe) {
safe->info();
delete safe;
}
return 0;
}
5. RAII (Resource Acquisition Is Initialization)
RAII adalah idiom paling penting di C++. Konsepnya: resource (memori, file, lock, dll) diakuisisi di constructor dan dilepas di destructor. Karena destructor dipanggil otomatis saat object keluar scope, resource management menjadi otomatis dan exception-safe.
Contoh RAII
#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
using namespace std;
// ===== RAII Wrapper untuk Heap Memory =====
template <typename T>
class RAIIArray {
private:
T* data;
size_t ukuran;
public:
explicit RAIIArray(size_t n) : ukuran(n), data(new T[n]()) {
cout << "RAII Array: " << n << " elemen dialokasikan" << endl;
}
~RAIIArray() {
cout << "RAII Array: dealokasi otomatis" << endl;
delete[] data;
}
// Prevent copy
RAIIArray(const RAIIArray&) = delete;
RAIIArray& operator=(const RAIIArray&) = delete;
// Allow move
RAIIArray(RAIIArray&& other) noexcept
: data(other.data), ukuran(other.ukuran) {
other.data = nullptr;
other.ukuran = 0;
}
T& operator[](size_t index) { return data[index]; }
const T& operator[](size_t index) const { return data[index]; }
size_t size() const { return ukuran; }
};
// ===== RAII File Handler =====
class FileHandler {
private:
FILE* file;
public:
FileHandler(const char* filename, const char* mode) {
file = fopen(filename, mode);
if (!file) {
throw runtime_error(string("Gagal buka file: ") + filename);
}
cout << "File dibuka: " << filename << endl;
}
~FileHandler() {
if (file) {
fclose(file);
cout << "File ditutup otomatis (RAII)" << endl;
}
}
// Prevent copy
FileHandler(const FileHandler&) = delete;
FileHandler& operator=(const FileHandler&) = delete;
void write(const string& data) {
fprintf(file, "%s", data.c_str());
}
string readLine() {
char buffer[256];
if (fgets(buffer, sizeof(buffer), file)) {
return string(buffer);
}
return "";
}
bool isOpen() const { return file != nullptr; }
};
// ===== RAII Lock (simulasi) =====
class MutexGuard {
private:
bool& locked;
public:
MutexGuard(bool& mtx) : locked(mtx) {
if (locked) throw runtime_error("Already locked!");
locked = true;
cout << "Lock acquired (RAII)" << endl;
}
~MutexGuard() {
locked = false;
cout << "Lock released (RAII)" << endl;
}
MutexGuard(const MutexGuard&) = delete;
MutexGuard& operator=(const MutexGuard&) = delete;
};
int main() {
// RAII Array — otomatis dealokasi
cout << "=== RAII Array ===" << endl;
{
RAIIArray<int> arr(5);
for (size_t i = 0; i < arr.size(); i++) {
arr[i] = (i + 1) * 100;
}
for (size_t i = 0; i < arr.size(); i++) {
cout << arr[i] << " ";
}
cout << endl;
} // destructor otomatis dealokasi
// RAII File Handler
cout << "\n=== RAII File ===" << endl;
try {
FileHandler fh("test_raii.txt", "w");
fh.write("Hello from RAII!\n");
fh.write("File ditutup otomatis\n");
// fh ditutup otomatis di destructor, bahkan jika exception!
} catch (const exception& e) {
cout << "Error: " << e.what() << endl;
}
// RAII Lock
cout << "\n=== RAII Lock ===" << endl;
{
bool mutex = false;
{
MutexGuard guard(mutex);
cout << "Doing critical work..." << endl;
// guard dihapus di sini → lock dilepas
}
cout << "mutex unlocked: " << (!mutex ? "Ya" : "Tidak") << endl;
}
// std::fstream juga RAII!
cout << "\n=== std::fstream RAII ===" << endl;
{
ofstream out("test_fstream.txt");
out << "Ditulis oleh ofstream" << endl;
// File otomatis ditutup di destructor
}
return 0;
}
RAII membuat kode exception-safe — resource dilepas bahkan ketika exception terjadi. Tanpa RAII, setiap titik return atau exception bisa menyebabkan resource leak. RAII adalah alasan mengapa C++ tidak butuh finally block seperti Java atau Python.
6. Smart Pointers
Smart pointers adalah implementasi RAII untuk heap memory. Mereka otomatis mengelola alokasi dan dealokasi, sehingga programmer tidak perlu memanggil delete secara manual.
#include <iostream>
#include <memory>
#include <string>
#include <vector>
using namespace std;
class Resource {
string nama;
public:
Resource(const string& n) : nama(n) {
cout << "Resource " << nama << " dialokasi" << endl;
}
~Resource() {
cout << "Resource " << nama << " didealokasi" << endl;
}
void use() const { cout << "Menggunakan " << nama << endl; }
};
// ===== unique_ptr: Single ownership =====
void demoUnique() {
cout << "=== unique_ptr ===" << endl;
// Pembuatan
auto r1 = make_unique<Resource>("Database");
r1->use();
// Transfer ownership
auto r2 = move(r1);
if (!r1) cout << "r1 sudah null setelah move" << endl;
r2->use();
// Array of unique_ptr
vector<unique_ptr<Resource>> resources;
resources.push_back(make_unique<Resource>("Cache"));
resources.push_back(make_unique<Resource>("Queue"));
for (const auto& r : resources) {
r->use();
}
// Semua otomatis dihapus di akhir scope
}
// ===== shared_ptr: Shared ownership =====
void demoShared() {
cout << "\n=== shared_ptr ===" << endl;
auto r = make_shared<Resource>("Logger");
cout << "Ref count: " << r.use_count() << endl; // 1
{
auto r2 = r; // Copy → ref count++
cout << "Ref count: " << r.use_count() << endl; // 2
r2->use();
}
cout << "Ref count: " << r.use_count() << endl; // 1
// Object dihapus ketika ref count = 0
}
// ===== weak_ptr: Observer (tidak menambah ref count) =====
void demoWeak() {
cout << "\n=== weak_ptr ===" << endl;
weak_ptr<Resource> weak;
{
auto shared = make_shared<Resource>("Session");
weak = shared;
if (auto locked = weak.lock()) {
locked->use(); // Safe access
cout << "Ref count: " << locked.use_count() << endl; // 1 (weak tidak dihitung!)
}
} // shared dihapus di sini
if (weak.expired()) {
cout << "Resource sudah dihapus (expired)" << endl;
}
}
// ===== Custom Deleter =====
void demoCustomDeleter() {
cout << "\n=== Custom Deleter ===" << endl;
// Untuk resource yang bukan C++ object (C-style API, FILE*, handle, dll)
auto fileDeleter = [](FILE* f) {
if (f) {
fclose(f);
cout << "FILE ditutup via custom deleter" << endl;
}
};
{
unique_ptr<FILE, decltype(fileDeleter)> fp(
fopen("test_custom.txt", "w"), fileDeleter
);
if (fp) {
fprintf(fp.get(), "Written via unique_ptr with custom deleter\n");
}
} // file ditutup otomatis via custom deleter
}
int main() {
demoUnique();
demoShared();
demoWeak();
demoCustomDeleter();
cout << "\n=== Program selesai ===" << endl;
return 0;
}
7. Memory Leaks
Memory leak terjadi ketika memori dialokasikan tetapi tidak pernah didealokasi. Dalam program jangka panjang (server, game engine), memory leak menyebabkan penggunaan RAM terus meningkat hingga crash.
Contoh Memory Leaks
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
// ❌ LEAK 1: Lupa delete
void leak1() {
int* data = new int[1000];
// ... menggunakan data ...
// LUPA delete[] data! → LEAK!
}
// ❌ LEAK 2: Exception sebelum delete
void leak2() {
int* data = new int[1000];
// ... proses ...
throw runtime_error("Error!"); // ← delete tidak pernah tercapai!
delete[] data; // Dead code
}
// ❌ LEAK 3: Overwriting pointer
void leak3() {
int* ptr = new int(42);
ptr = new int(99); // ← Memory int(42) hilang! Tidak bisa di-delete
delete ptr; // Hanya menghapus int(99)
}
// ❌ LEAK 4: Circular reference (shared_ptr)
struct Node {
shared_ptr<Node> next; // ← Circular reference!
string nama;
~Node() { cout << "Node " << nama << " dihapus" << endl; }
};
void leak4() {
auto a = make_shared<Node>();
a->nama = "A";
auto b = make_shared<Node>();
b->nama = "B";
a->next = b; // a → b
b->next = a; // b → a (circular!)
// Ref count tidak pernah 0 → LEAK!
}
// ✅ SOLUSI untuk semua masalah di atas
// Solusi 1 & 2: Gunakan smart pointers
void solved1() {
auto data = make_unique<int[]>(1000);
// Otomatis dealokasi, bahkan jika exception!
}
// Solusi 3: Gunakan smart pointer langsung
void solved3() {
auto ptr = make_unique<int>(42);
ptr = make_unique<int>(99); // int(42) otomatis dihapus
}
// Solusi 4: Gunakan weak_ptr untuk break circular reference
struct FixedNode {
string nama;
weak_ptr<FixedNode> next; // ← weak_ptr memecah circular!
~FixedNode() { cout << "Node " << nama << " dihapus" << endl; }
};
void solved4() {
auto a = make_shared<FixedNode>();
a->nama = "A";
auto b = make_shared<FixedNode>();
b->nama = "B";
a->next = b;
b->next = a; // weak_ptr tidak menambah ref count
// Ref count = 1 masing-masing → dealokasi normal!
}
int main() {
// Demonstrasi circular reference solution
cout << "=== Circular Reference Fixed ===" << endl;
solved4();
cout << "\n=== Program selesai ===" << endl;
return 0;
}
8. Dangling Pointers
Dangling pointer adalah pointer yang masih menyimpan alamat memori yang sudah didealokasi. Mengakses dangling pointer adalah undefined behavior — bisa crash, bisa menghasilkan nilai sampah, atau tampak "berhasil" tapi korupsi data.
#include <iostream>
#include <memory>
using namespace std;
// ❌ DANGLING 1: Return pointer ke lokal variabel
int* bahayaReturnLocal() {
int x = 42;
return &x; // ← x hilang setelah fungsi selesai! DANGLING!
}
// ✅ Solusi: return by value, atau heap allocation
int amanReturn() {
return 42; // Return by value — aman
}
// ❌ DANGLING 2: Akses setelah delete
void danglingAfterDelete() {
int* ptr = new int(100);
delete ptr;
// ptr masih menyimpan alamat lama!
// cout << *ptr; // ← UNDEFINED BEHAVIOR!
ptr = nullptr; // ✅ Set ke null setelah delete
}
// ❌ DANGLING 3: Reference ke temporary
void danglingReference() {
// const int& ref = 42; // C++ extends lifetime for const ref
// Tapi ini berbahaya:
// int&& ref = 42;
// auto ptr = &ref; // ← Berbahaya!
}
// Demonstrasi: dangling pointer terlihat "berhasil"
void deceptiveDangling() {
int* ptr = new int(999);
cout << "Before delete: " << *ptr << endl;
delete ptr;
// Ini SANGAT berbahaya karena tampak "berhasil"
// cout << "After delete: " << *ptr << endl;
// Mungkin cetak 999, mungkin garbage, mungkin crash!
// NEVER do this!
ptr = nullptr; // ← Satu-satunya cara aman
if (ptr != nullptr) {
cout << *ptr << endl; // Tidak akan pernah tercapai
}
}
int main() {
cout << "=== Solusi Dangling Pointers ===" << endl;
// 1. Selalu set nullptr setelah delete
int* p = new int(10);
delete p;
p = nullptr; // Wajib!
// 2. Gunakan smart pointers
{
auto sp = make_unique<int>(42);
cout << *sp << endl;
} // sp otomatis invalid setelah scope
// 3. Gunakan references daripada pointers jika mungkin
int value = 100;
int& ref = value; // Reference — tidak bisa dangling (must bind at init)
ref += 50;
cout << "Ref: " << value << endl; // 150
// 4. Cek pointer sebelum dereference
int* maybe_null = nullptr;
if (maybe_null) {
cout << *maybe_null << endl; // Tidak tercapai
} else {
cout << "Pointer null!" << endl;
}
return 0;
}
9. Buffer Overflow
Buffer overflow terjadi ketika menulis data melebihi batas buffer yang dialokasikan. Ini adalah salah satu vulnerability keamanan paling umum dan berbahaya.
#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <cstring>
#include <algorithm>
using namespace std;
int main() {
// ❌ Buffer overflow — bahaya!
// char name[5];
// strcpy(name, "Hello World!"); // ← OVERFLOW! 12 char ke buffer 5
// ✅ Solusi 1: Gunakan std::string
string nama = "Hello World!"; // Otomatis menyesuaikan ukuran
cout << nama << endl;
// ✅ Solusi 2: Gunakan std::vector
vector<int> data(5);
data[0] = 10; // Bounds checked via .at()
// data.at(100); // ← Throws std::out_of_range!
// ✅ Solusi 3: Gunakan std::array (compile-time size)
array<int, 5> arr = {1, 2, 3, 4, 5};
cout << arr.at(2) << endl; // Bounds checked
// arr.at(10); // ← Throws!
// ✅ Solusi 4: Bounds checking manual
int buffer[10];
int index = 15;
if (index >= 0 && index < 10) {
buffer[index] = 42; // Safe
} else {
cout << "Index out of bounds!" << endl;
}
// ✅ Solusi 5: strncpy alih-alih strcpy
char dest[10];
strncpy(dest, "Hello World!", sizeof(dest) - 1);
dest[sizeof(dest) - 1] = '\0'; // Pastikan null-terminated
cout << "dest: " << dest << endl; // "Hello Worl" (truncated)
// ✅ Solusi 6: std::span (C++20) — view ke array dengan bounds
// std::span<int> sp(arr); // Checked access
// ✅ Solusi 7: Range-based for — tidak bisa overflow
for (int x : arr) {
cout << x << " ";
}
cout << endl;
// ✅ Solusi 8: std::copy_n — batasi jumlah copy
int src[] = {10, 20, 30, 40, 50};
int dst[3];
copy_n(src, 3, dst); // Hanya copy 3 elemen
return 0;
}
10. Debugging dengan Valgrind
Valgrind adalah tool gratis untuk mendeteksi memory leaks, akses illegal, dan masalah memori lainnya. Ini adalah tool wajib bagi setiap C++ programmer.
Instalasi Valgrind
# Linux (Ubuntu/Debian) sudo apt install valgrind -y # macOS brew install valgrind # Windows: Gunakan WSL atau Docker # Kompilasi dengan debug symbols (-g) dan tanpa optimasi g++ -g -O0 -std=c++17 program.cpp -o program # Jalankan Valgrind valgrind --leak-check=full --show-leak-kinds=all ./program # Output contoh: # ==12345== HEAP SUMMARY: # ==12345== in use at exit: 0 bytes in 0 blocks # ==12345== total heap usage: 5 allocs, 5 frees, 72,704 bytes allocated # ==12345== # ==12345== All heap blocks were freed -- no leaks are possible
Contoh Debugging
// File: debug_example.cpp
// Kompilasi: g++ -g -O0 -o debug debug_example.cpp
// Jalankan: valgrind --leak-check=full ./debug
#include <iostream>
#include <memory>
using namespace std;
// Contoh dengan leak — Valgrind akan mendeteksi
void fungsiDenganLeak() {
int* data = new int[100];
data[0] = 42;
cout << "data[0] = " << data[0] << endl;
// ❌ Lupa delete[] data → Valgrind melaporkan leak
}
// Contoh bersih — Valgrind tidak menemukan masalah
void fungsiBersih() {
auto data = make_unique<int[]>(100);
data[0] = 42;
cout << "data[0] = " << data[0] << endl;
// ✅ Otomatis dealokasi
}
// Contoh out-of-bounds — Valgrind mendeteksi
void outOfBounds() {
int* arr = new int[5]{1, 2, 3, 4, 5};
// cout << arr[10] << endl; // ← Valgrind: "Invalid read of size 4"
delete[] arr;
}
int main() {
cout << "=== Test 1: Clean ===" << endl;
fungsiBersih();
cout << "\n=== Test 2: Leak ===" << endl;
fungsiDenganLeak();
cout << "\n=== Test 3: Bounds ===" << endl;
outOfBounds();
return 0;
}
// Valgrind output untuk di atas:
// ==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
// ==12345== at 0x...: operator new[](unsigned long) (vg_replace_malloc.c:...)
// ==12345== by 0x...: fungsiDenganLeak() (debug_example.cpp:...)
// → Memberitahu PERSIS di mana leak terjadi!
Alternatif Valgrind
| Tool | Platform | Kelebihan |
|---|---|---|
| AddressSanitizer (ASan) | Cross-platform | Lebih cepat dari Valgrind, compile flag: -fsanitize=address |
| LeakSanitizer (LSan) | Linux/macOS | Fokus memory leak: -fsanitize=leak |
| ThreadSanitizer | Cross-platform | Deteksi race conditions: -fsanitize=thread |
| Dr. Memory | Windows/Linux | Alternatif Valgrind untuk Windows |
| Visual Studio Diagnostic | Windows | Built-in di Visual Studio, mudah digunakan |
# AddressSanitizer — mendeteksi buffer overflow, use-after-free, dll
g++ -g -fsanitize=address -fno-omit-frame-pointer program.cpp -o program
./program
# Error message akan sangat detail dan informatif
# LeakSanitizer (subset of ASan, lebih ringan)
g++ -g -fsanitize=leak program.cpp -o program
# MemorySanitizer — uninitialized memory reads
g++ -g -fsanitize=memory program.cpp -o program
# Gabungan sanitizer
g++ -g -fsanitize=address,undefined program.cpp -o program
# Di CMakeLists.txt:
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -g")
11. Best Practices Memory Management
Berikut rangkuman best practices untuk memory management yang aman dan efisien di C++:
| No. | Best Practice | Penjelasan |
|---|---|---|
| 1 | Gunakan RAII | Selalu bungkus resource dalam class dengan destructor yang melepas resource |
| 2 | Prefer smart pointers | Gunakan unique_ptr sebagai default, shared_ptr hanya saat perlu shared ownership |
| 3 | Hindari raw new/delete | Jika harus, pastikan setiap new punya pasangan delete yang pasti tercapai |
| 4 | Set nullptr setelah delete | Mencegah dangling pointer: delete ptr; ptr = nullptr; |
| 5 | Gunakan std::vector | Daripada raw array di heap, vector mengelola memori otomatis |
| 6 | Gunakan std::string | Daripada char array, string menghindari buffer overflow |
| 7 | Break circular references | Gunakan weak_ptr untuk memecah circular reference di shared_ptr |
| 8 | Test dengan sanitizer | Kompilasi dengan -fsanitize=address selama development |
| 9 | Gunakan RAII untuk semua resource | File, lock, network connection — semua harus RAII |
| 10 | Ikuti Rule of Zero/Five | Jika class mengelola resource, definisikan semua 5 special functions |
Rule of Zero dan Rule of Five
#include <iostream>
#include <string>
#include <memory>
using namespace std;
// === RULE OF ZERO ===
// Jika class TIDAK mengelola resource langsung,
// jangan definisikan special functions apapun.
// Gunakan anggota yang sudah RAII (string, vector, unique_ptr).
class Mahasiswa {
string nama; // ← RAII, auto-managed
vector<int> nilai; // ← RAII, auto-managed
unique_ptr<int> data; // ← RAII, auto-managed
public:
Mahasiswa(string n) : nama(move(n)) {}
// Tidak perlu destructor, copy, move — compiler-generated OK!
};
// === RULE OF FIVE ===
// Jika class MENGELola resource, definisikan SEMUA 5:
// 1. Destructor
// 2. Copy constructor
// 3. Copy assignment
// 4. Move constructor
// 5. Move assignment
class Buffer {
int* data;
size_t ukuran;
public:
Buffer(size_t n) : ukuran(n), data(new int[n]()) {}
// 1. Destructor
~Buffer() { delete[] data; }
// 2. Copy constructor (deep copy)
Buffer(const Buffer& other)
: ukuran(other.ukuran), data(new int[other.ukuran]) {
copy(other.data, other.data + ukuran, data);
}
// 3. Copy assignment (copy-and-swap idiom)
Buffer& operator=(Buffer other) { // Pass by value = copy
swap(data, other.data);
swap(ukuran, other.ukuran);
return *this;
}
// 4. Move constructor
Buffer(Buffer&& other) noexcept
: data(other.data), ukuran(other.ukuran) {
other.data = nullptr;
other.ukuran = 0;
}
// 5. Move assignment
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
ukuran = other.ukuran;
other.data = nullptr;
other.ukuran = 0;
}
return *this;
}
};
int main() {
// Rule of Zero — otomatis
Mahasiswa m1("Budi");
// Rule of Five — explicit management
Buffer b1(100);
Buffer b2 = b1; // Copy constructor
Buffer b3 = move(b1); // Move constructor
b2 = b3; // Copy assignment
b3 = Buffer(200); // Move assignment
cout << "Semua berjalan dengan aman!" << endl;
return 0;
}
12. Quiz: Uji Pemahamanmu!
Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut untuk menguji pemahamanmu tentang Memory Management di C++: