int x = 42;
int *ptr = &x; // ptr holds address of x
*ptr = 100; // dereference: change x to 100
int **pp = &ptr; // pointer to pointer
// NULL pointer
int *p = NULL; // or nullptr (C++11)
if (p != NULL) { *p = 5; } // always check!
// Pointer arithmetic
int arr[] = {1,2,3,4,5};
int *p = arr; // points to arr[0]
p++; // now points to arr[1]
*(p + 2) // arr[3]
p[2] // same as *(p+2)
// const with pointers
const int *p // can't modify *p
int *const p // can't modify p itself
const int *const p // neither
#include <stdlib.h>
// malloc — uninitialized memory
int *arr = (int*)malloc(5 * sizeof(int));
if (arr == NULL) { /* handle error */ }
// calloc — zero-initialized
int *arr = (int*)calloc(5, sizeof(int));
// realloc — resize
arr = (int*)realloc(arr, 10 * sizeof(int));
// free — ALWAYS free heap memory!
free(arr);
arr = NULL; // prevent dangling pointer
// Typical pattern: allocate, use, free
int *buf = (int*)malloc(n * sizeof(int));
if (!buf) return ENOMEM;
// ... use buf ...
free(buf);
// Define struct
struct Student {
char name[50];
int roll_no;
float cgpa;
};
// Usage
struct Student s1;
s1.roll_no = 101;
strcpy(s1.name, "Alice");
// Struct pointer
struct Student *sp = &s1;
sp->roll_no = 102; // -> for pointer
(*sp).cgpa = 9.1; // equivalent
// typedef
typedef struct {
float x, y;
} Point;
Point p = {3.0, 4.0};
// Dynamically allocated struct
Student *sp = (Student*)malloc(sizeof(Student));
free(sp);
class BankAccount {
private:
double balance; // private by default in class
public:
// Constructor
BankAccount(double initial) : balance(initial) {}
// Destructor (cleanup)
~BankAccount() { /* cleanup */ }
// Methods
void deposit(double amount) {
if (amount > 0) balance += amount;
}
double getBalance() const { return balance; }
// Operator overloading
BankAccount operator+(const BankAccount& b) {
return BankAccount(balance + b.balance);
}
};
// Usage
BankAccount acc(1000.0);
acc.deposit(500);
BankAccount *acp = new BankAccount(0);
delete acp; // ALWAYS delete!
| Stack | Heap | |
|---|---|---|
| Allocation | Automatic (declaration) | Manual (malloc/new) |
| Deallocation | Automatic (scope end) | Manual (free/delete) |
| Size | Limited (~1-8MB) | Large (GBs) |
| Speed | Fast | Slower |
| Lifetime | Until end of scope | Until explicitly freed |
| Use for | Local variables, small arrays | Dynamic, large, long-lived data |
| Error | Cause | Detection |
|---|---|---|
| Memory leak | malloc without free | Valgrind, address sanitizer |
| Dangling pointer | Using freed memory | Set to NULL after free |
| Buffer overflow | Write beyond array bounds | AddressSanitizer |
| Double free | free() called twice | Crash / undefined behavior |
| Null deref | *NULL pointer | Segfault |
| Stack overflow | Deep recursion / large local vars | Crash with SIGSEGV |
#include <memory>
// unique_ptr — single owner, auto-deleted
auto p = std::make_unique<int>(42);
// Automatically deleted when p goes out of scope
// shared_ptr — reference counted
auto sp1 = std::make_shared<Student>("Alice");
auto sp2 = sp1; // refcount = 2
// Deleted when refcount drops to 0
// weak_ptr — no ownership (break cycles)
std::weak_ptr<Node> wp = sp;
if (auto locked = wp.lock()) {
// use locked if still alive
}
// RAII pattern (Resource Acquisition Is Init)
// Constructor acquires, destructor releases
// smart pointers implement this automatically