Memory Layout of a C/C++ Process
Before diving in to allocation APIs, it helps to know where objects live.
int globalVal = 1;
static int staticGlobalVal = 1;
void demo()
{
static int staticVal = 1;
int localVal = 1;
int nums[10] = {1, 2, 3, 4};
char buf[] = "abcd";
const char* literal = "abcd";
int* p1 = (int*)malloc(sizeof(int) * 4);
int* p2 = (int*)calloc(4, sizeof(int));
int* p3 = (int*)realloc(p2, sizeof(int) * 4);
free(p1);
free(p3);
}
Quick sizing quiz:
sizeof(nums)= 40 bytessizeof(buf)= 5 bytes (4 chars + NUL)strlen(buf)= 4sizeof(literal)= 4 or 8 (pointer size)sizeof(p1)= 4 or 8 (pointer size)
Classic C Allocation Primitives
int* a = (int*)malloc(sizeof(int)); // uninitialized
int* b = (int*)calloc(4, sizeof(int)); // zero-initialized
int* c = (int*)realloc(b, 10*sizeof(int)); // resize; may move
free(a);
free(c); // note: no need to free(b) if realloc moved it
Key differences
malloc– raw byte count, indeterminate content.calloc– element count × size, zeroed bytes.realloc– grow/shrink; returns new pointer if block moved.
C++ Allocation: new / delete
Built-in Types
int* p1 = new int; // uninitialized
int* p2 = new int(42); // initialized to 42
int* p3 = new int[5]; // array of 5 ints (uninitialized)
int* p4 = new int[5]{1,2}; // aggregate-init, rest zero
delete p1;
delete p2;
delete[] p3;
delete[] p4;
Always match new with delete and new[] with delete[].
User-Defined Types
class Widget {
public:
Widget(int v = 0) : val(v) { std::cout << "Ctor " << val << '\n'; }
~Widget() { std::cout << "Dtor " << val << '\n'; }
private:
int val;
};
Widget* w1 = (Widget*)malloc(sizeof(Widget)); // no construction
Widget* w2 = new Widget(99); // allocates + constructs
free(w1); // no destruction
delete w2; // destructs + deallocates
For arrays:
Widget* wa = new Widget[3]; // 3 default constructions
delete[] wa; // 3 destructions
Under the Hood: operator new / operator delete
new and delete are operators; the actual work is delegated to:
void* operator new(std::size_t n); // throws std::bad_alloc on failure
void operator delete(void*) noexcept;
These functions are merely thin wrappers around malloc/free that add exception-safety and bookkeeping. You can overload them for class-specific allocation strategies.
Array Cookie
When allocating new T[n] for a type with a non-trivial destructor, most implementations prepend a hidden cookie storing n. delete[] reads this cookie to know how many destructors to invoke. Omitting [] or mismatching leads to undefined behaviour.
Placement new
Construct an object in already-obtained storage:
#include <new>
char buffer[sizeof(Widget)];
Widget* pw = new (buffer) Widget(123); // placement new
pw->~Widget(); // explicit destruction
Useful for memory pools or containers that manage their own backing store.
malloc/free vs new/delete – Cheat Sheet
Memory Leaks
Definition: Memory that is no longer reachable but has not been released.
Consequences: Process RSS grows, swap thrashing, eventual OOM kill.
void leak_demo()
{
int* p = new int[1000];
// forgot delete[] p;
}
Leak Categories
- Heap leak –
new/mallocwithout matchingdelete/free. - Resource leak – Sockets, file descriptors, GDI handles, etc.
Detection & Prevention
- Windows:
_CrtDumpMemoryLeaks()at exit (coarse). - Linux:
valgrind --leak-check=full ./a.out. - Use RAII / smart pointers (
std::unique_ptr,std::shared_ptr). - Adopt container classes (
std::vector,std::string) instead of raw arrays. - Establish code-review rules: every
newmust have an owner with clear lifetime.