C++ heap vs stack
Date: 2021-01-03Last modified: 2024-02-26
Table of contents
Memory structure
Stack vs Heap Pros and Cons
Stack
- very fast access
- don’t have to explicitly de-allocate variables
- space is managed efficiently by CPU, memory will not become fragmented
- local variables only
- limit on stack size (OS-dependent)
- variables cannot be resized
Heap
- variables can be accessed globally
- no limit on memory size
- (relatively) slower access
- no guaranteed efficient use of space, memory may become fragmented over time as blocks of memory are allocated, then freed
- you must manage memory (you’re in charge of allocating and freeing variables)
- variables can be resized using realloc()
Dynamic Memory Allocation in C
C provides the following functions to allocate memory at runtime:
malloc
: allocates raw memory on the heapcalloc
: allocates raw memory and initializes to zerorealloc
: can change the size of memory blockfree
: releases the memory allocated by other functions
Memory Allocation
@endpikchr
Example of use
int a1; // stack
int a2; // stack
int a3; // stack
int a4; // stack
int *p1 = new int[5]; // heap
int *p2 = new int[4]; // heap
int *p3 = new int[3]; // heap
int *p4 = new int[2]; // heap
int *p5 = new int[1]; // heap; until p5 takes the next 32 bytes block
int *p6 = new int[7]; // heap; long jump here!!!
cout << &a1 << " stack a1\n";
cout << &a2 << " stack a2: " << long(&a2) - long(&a1) << " bytes from a1\n";
cout << &a3 << " stack a3: " << long(&a3) - long(&a2) << " bytes from a2\n";
cout << &a4 << " stack a4: " << long(&a4) - long(&a3) << " bytes from a3\n";
cout << p1 << " heap p1\n";
cout << &p1[0] << " heap p1[0]\n";
cout << &p1[1] << " heap p1[1]\n";
cout << &p1[2] << " heap p1[2]\n";
cout << &p1[3] << " heap p1[3]\n";
cout << &p1[4] << " heap p1[4]\n";
cout << p2 << " heap p2: " << long(p2) - long(p1) << " bytes from p1\n";
cout << &p2[0] << " heap p2[0]\n";
cout << &p2[1] << " heap p2[1]\n";
cout << &p2[2] << " heap p2[2]\n";
cout << &p2[3] << " heap p2[3]\n";
cout << p3 << " heap p3: " << long(p3) - long(p2) << " bytes from p2\n";
cout << p4 << " heap p4: " << long(p4) - long(p3) << " bytes from p3\n";
cout << p5 << " heap p5: " << long(p5) - long(p4) << " bytes from p4\n";
cout << p6 << " heap p6: " << long(p6) - long(p5) << " bytes from p5\n";
delete[] p1;
delete[] p2;
delete[] p3;
delete[] p4;
delete[] p5;
delete[] p6;
// The pointer itself is allocated on the stack
// The dynamic memeory is allocated on the heap
// Typical metadata = size & allocation status
// If the size is aligned to > 2 bytes,
// can use bottom bit of size for allocated bit;
auto printHeader = [](auto size) {
int *ptr = static_cast<int *>(calloc(size, sizeof(int))); // heap
size_t * headerBytePtr = (size_t *)ptr;
headerBytePtr--;
fmt::print("{:16b} {:6d} {:6d} {:6d}\n", *headerBytePtr, *headerBytePtr, size * sizeof(int), size);
free(ptr);
};
fmt::print("sizeof(size_t) = {} bytes\n", sizeof(size_t));
for (int i = 1; i <= 32; ++i) {
printHeader(i);
}
printHeader(100);
printHeader(200);
printHeader(1000);
printHeader(10000);
Possible output
0x7ffc73f14f0c stack a1
0x7ffc73f14f08 stack a2: -4 bytes from a1
0x7ffc73f14f04 stack a3: -4 bytes from a2
0x7ffc73f14f00 stack a4: -4 bytes from a3
0x55f437da81c0 heap p1
0x55f437da81c0 heap p1[0]
0x55f437da81c4 heap p1[1]
0x55f437da81c8 heap p1[2]
0x55f437da81cc heap p1[3]
0x55f437da81d0 heap p1[4]
0x55f437da81e0 heap p2: 32 bytes from p1
0x55f437da81e0 heap p2[0]
0x55f437da81e4 heap p2[1]
0x55f437da81e8 heap p2[2]
0x55f437da81ec heap p2[3]
0x55f437da8200 heap p3: 32 bytes from p2
0x55f437da8220 heap p4: 32 bytes from p3
0x55f437da8240 heap p5: 32 bytes from p4
0x55f437d9eda0 heap p6: -38048 bytes from p5
sizeof(size_t) = 8 bytes
100001 33 4 1
100001 33 8 2
100001 33 12 3
100001 33 16 4
100001 33 20 5
100001 33 24 6
110001 49 28 7
110001 49 32 8
110001 49 36 9
110001 49 40 10
1010001 81 44 11
1000001 65 48 12
1000001 65 52 13
1000001 65 56 14
1010001 81 60 15
1010001 81 64 16
1010001 81 68 17
1010001 81 72 18
1110001 113 76 19
1100001 97 80 20
1100001 97 84 21
1100001 97 88 22
1110001 113 92 23
1110001 113 96 24
1110001 113 100 25
1110001 113 104 26
10000001 129 108 27
10000001 129 112 28
10000001 129 116 29
10000001 129 120 30
10010001 145 124 31
10010001 145 128 32
110100001 417 400 100
1100110001 817 800 200
111110110001 4017 4000 1000
1001110001010001 40017 40000 10000