Example Code: Memory Management#
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | #include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#ifdef __APPLE__
// Macos hasn't implemented the C11 aligned_alloc as of the time 2019/8.
void * aligned_alloc(size_t alignment, size_t size)
{
void * ptr;
posix_memalign(&ptr, alignment, size);
return ptr;
}
#endif
void outer();
int main(int argc, char ** argv)
{
printf("frame address of main: %p\n", __builtin_frame_address(0));
outer();
return 0;
}
// Will be called by outer();
int64_t * inner()
{
printf("frame address of inner: %p\n", __builtin_frame_address(0));
// An array on the stack. It is popped away when execution leaves this
// function. You cannot use the memory outside this function.
int64_t data_stack[32];
for (size_t it = 0; it < 32; ++it)
{
data_stack[it] = 100 + it;
}
printf("stack memory: %p\n", data_stack);
// A dynamic array.
int64_t * data_dynamic = (int64_t *) malloc(32 * sizeof(int64_t));
for (size_t it = 0; it < 32; ++it)
{
data_dynamic[it] = 200 + it;
}
printf("dynamic memory: %p\n", data_dynamic);
return data_dynamic;
}
void outer()
{
printf("frame address of outer: %p\n", __builtin_frame_address(0));
int64_t * data = inner(); // Initialize the data.
printf("data returned from inner: %p\n", data);
for (size_t it = 0; it < 32; ++it)
{
if (data[it] != 200 + it)
{
printf("error\n");
}
}
printf("=== malloc tested\n");
// You must free the memory after you finish using it. Otherwise it will
// remain in the process unclaimed, results in the "memory leak".
free(data);
//free(data); // Double free results into error.
printf("=== free tested\n");
#if 0
// You may not use the memory that is already freed. The results is
// undefined.
for (size_t it = 0; it < 32; ++it)
{
if (data[it] != 200 + it)
{
printf("error\n");
}
}
#endif
// The following two allocations result in the same zero-initialized array.
//
// The first one uses calloc. If the OS returns the memory that is already
// zero-initialized, calloc knows, and it doesn't need to redo the zero
// initialization.
data = (int64_t *) calloc(32, sizeof(int64_t));
free(data);
// The second one uses malloc and manual initialization. The malloc call
// does not provide any information about whether the memory is already
// zero-initialized.
data = (int64_t *) malloc(32 * sizeof(int64_t));
// Even if the allocated memory was already zero-initialized by the OS, we
// still need to do the initialization.
for (size_t it = 0; it < 32; ++it) { data[it] = 0; }
free(data);
printf("=== calloc tested\n");
// Reallocate the memory with smaller or larger size.
data = (int64_t *) malloc((1UL << 20) * 2 * sizeof(int64_t));
printf("address by malloc: %p\n", data);
data = (int64_t *) realloc(data, (1UL << 20) * 1 * sizeof(int64_t));
printf("address by realloc to smaller memory: %p\n", data);
data = (int64_t *) realloc(data, (1UL << 20) * 4 * sizeof(int64_t));
printf("address by realloc to larger memory: %p\n", data);
free(data);
printf("=== realloc tested\n");
// Aligned allocation.
int64_t * data1 = (int64_t *) malloc(sizeof(int64_t));
printf("address by malloc: %p\n", data1);
int64_t * data2 = (int64_t *) aligned_alloc(256, 256 * sizeof(int64_t));
printf("address by aligned_alloc: %p\n", data2);
free(data1);
free(data2);
printf("=== aligned_alloc tested\n");
}
|
Example code for C++ memory management (
cppmem.cpp
).#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 | #include <cstdlib>
#include <iostream>
/*
* A dummy class taking 8k bytes.
*/
struct Block
{
Block()
{
std::cout << "Block (" << this << ") constructed" << std::endl;
}
~Block()
{
std::cout << "Block (" << this << ") destructed" << std::endl;
}
int64_t buffer[1024];
};
void scalar_form()
{
std::cout
<< "frame address of scalar_form: " << __builtin_frame_address(0)
<< std::endl;
// Doing this place 8k bytes on stack.
Block block_stack;
for (size_t it = 0; it < 1024; ++it)
{
block_stack.buffer[it] = 1000 + it;
}
std::cout << "object on stack: " << &block_stack << std::endl;
std::cout
<< "address difference: "
<< reinterpret_cast<std::size_t>(__builtin_frame_address(0))
- reinterpret_cast<std::size_t>(&block_stack)
<< ", sizeof(Block): " << sizeof(Block)
<< std::endl;
// Use the new expression. Note that this "new" is an expression. It
// calls the operator ("::operator new"), but not the operator itself.
Block * block_dynamic = new Block;
std::cout << "object on dynamic memory: " << block_dynamic << std::endl;
for (size_t it = 0; it < 1024; ++it)
{
block_dynamic->buffer[it] = 2000 + it;
}
std::cout << "=== new tested" << std::endl;
// The delete expression that destruct and deallocate the memory of the
// dynamic block object. Similarly, the expression calls ::operator delete
// for block_dynamic.
delete block_dynamic;
std::cout << "=== delete tested" << std::endl;
}
void array_form()
{
// An array on the stack. It is popped away when execution leaves this
// function. You cannot use the memory outside this function.
int64_t data_stack[32];
for (size_t it = 0; it < 32; ++it)
{
data_stack[it] = 100 + it;
}
std::cout << "stack array memory: " << data_stack << std::endl;
// A dynamic array.
int64_t * data_dynamic = new int64_t[32];
for (size_t it = 0; it < 32; ++it)
{
data_dynamic[it] = 200 + it;
}
std::cout << "dynamic array memory: " << data_dynamic << std::endl;
std::cout << "=== new[] tested" << std::endl;
delete[] data_dynamic;
std::cout << "=== delete[] tested" << std::endl;
}
void placement()
{
char * buffer = new char[sizeof(Block)];
Block * block = new (buffer) Block;
for (size_t it = 0; it < 1024; ++it)
{
block->buffer[it] = it;
}
std::cout << "=== placement new tested" << std::endl;
#if 0
// This induces undefined behavior. Don't do this.
delete block;
#endif
// Instead of deleting the pointer block, call explicit the destructor and
// delete the original buffer.
block->~Block();
delete[] buffer;
}
int main(int argc, char ** argv)
{
scalar_form();
array_form();
placement();
return 0;
}
|
Build cppmem.cpp.#
$ g++ cppmem.cpp -o cppmem -std=c++17 -O3 -g
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | #include <cstdlib>
#include <new>
#include <memory>
#include <limits>
#include <atomic>
#include <vector>
#include <iostream>
struct ByteCounterImpl
{
std::atomic_size_t allocated = 0;
std::atomic_size_t deallocated = 0;
std::atomic_size_t refcount = 0;
}; /* end struct ByteCounterImpl */
/**
* One instance of this counter is shared among a set of allocators.
*
* The counter keeps track of the bytes allocated and deallocated, and report
* those two numbers in addition to bytes that remain allocated.
*/
class ByteCounter
{
public:
ByteCounter()
: m_impl(new ByteCounterImpl)
{ incref(); }
ByteCounter(ByteCounter const & other)
: m_impl(other.m_impl)
{ incref(); }
ByteCounter & operator=(ByteCounter const & other)
{
if (&other != this)
{
decref();
m_impl = other.m_impl;
incref();
}
return *this;
}
ByteCounter(ByteCounter && other)
: m_impl(other.m_impl)
{ incref(); }
ByteCounter & operator=(ByteCounter && other)
{
if (&other != this)
{
decref();
m_impl = other.m_impl;
incref();
}
return *this;
}
~ByteCounter() { decref(); }
void swap(ByteCounter & other)
{
std::swap(m_impl, other.m_impl);
}
void increase(std::size_t amount)
{
m_impl->allocated += amount;
}
void decrease(std::size_t amount)
{
m_impl->deallocated += amount;
}
std::size_t bytes() const { return m_impl->allocated - m_impl->deallocated; }
std::size_t allocated() const { return m_impl->allocated; }
std::size_t deallocated() const { return m_impl->deallocated; }
/* This is for debugging. */
std::size_t refcount() const { return m_impl->refcount; }
private:
void incref() { ++m_impl->refcount; }
void decref()
{
if (nullptr == m_impl)
{
// Do nothing.
}
else if (1 == m_impl->refcount)
{
delete m_impl;
m_impl = nullptr;
}
else
{
--m_impl->refcount;
}
}
friend bool operator==(ByteCounter const & a, ByteCounter const & b);
ByteCounterImpl * m_impl;
}; /* end class ByteCounter */
bool operator==(ByteCounter const & a, ByteCounter const & b)
{
return a.m_impl == b.m_impl;
}
/**
* Very simple allocator that counts the number of bytes allocated through it.
*
* It's made to demonstrate the STL allocator and only works in this example.
* A lot of modification is needed to use it in a real application.
*/
template <class T>
struct MyAllocator
{
using value_type = T;
// Just use the default constructor of ByteCounter for the data member
// "counter".
MyAllocator() = default;
template <class U> constexpr
MyAllocator(const MyAllocator<U> & other) noexcept
{
counter = other.counter;
}
T * allocate(std::size_t n)
{
if (n > std::numeric_limits<std::size_t>::max() / sizeof(T))
{
throw std::bad_alloc();
}
const std::size_t bytes = n*sizeof(T);
T * p = static_cast<T *>(std::malloc(bytes));
if (p)
{
counter.increase(bytes);
return p;
}
else
{
throw std::bad_alloc();
}
}
void deallocate(T* p, std::size_t n) noexcept
{
std::free(p);
const std::size_t bytes = n*sizeof(T);
counter.decrease(bytes);
}
ByteCounter counter;
}; /* end struct MyAllocator */
template <class T, class U>
bool operator==(const MyAllocator<T> & a, const MyAllocator<U> & b)
{
return a.counter == b.counter;
}
template <class T, class U>
bool operator!=(const MyAllocator<T> & a, const MyAllocator<U> & b)
{
return !(a == b);
}
template <class T>
std::ostream & operator << (std::ostream & out, const MyAllocator<T> & alloc)
{
out << "allocator: bytes = " << alloc.counter.bytes();
out << " allocated = " << alloc.counter.allocated();
out << " deallocated = " << alloc.counter.deallocated();
return out;
}
int main(int argc, char ** argv)
{
MyAllocator<size_t> alloc;
std::vector<size_t, MyAllocator<size_t>> vec1(alloc);
std::cout << alloc << std::endl;
for (size_t it=0; it<1024; ++it)
{
vec1.push_back(it);
}
std::cout << alloc << std::endl;
std::vector<size_t, MyAllocator<size_t>>(alloc).swap(vec1);
std::cout << alloc << std::endl;
std::vector<size_t, MyAllocator<size_t>> vec2(1024, alloc);
std::cout << alloc << std::endl;
std::vector<size_t, MyAllocator<size_t>> vec3(std::move(vec2));
std::cout << alloc << std::endl;
std::vector<size_t, MyAllocator<size_t>>(alloc).swap(vec3);
std::cout << alloc << std::endl;
return 0;
}
|
Template to help keep track of the number of living instances
(
icount.cpp
).#1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | #include <cstdlib>
#include <new>
#include <memory>
#include <limits>
#include <atomic>
#include <vector>
#include <algorithm>
#include <iostream>
template <class T>
class InstanceCounter
{
public:
InstanceCounter() { ++m_constructed; }
InstanceCounter(InstanceCounter const & other) { ++m_copied; }
~InstanceCounter() { ++m_destructed; }
static std::size_t active()
{
return m_constructed + m_copied - m_destructed;
}
static std::size_t constructed() { return m_constructed; }
static std::size_t copied() { return m_copied; }
static std::size_t destructed() { return m_destructed; }
private:
static std::atomic_size_t m_constructed;
static std::atomic_size_t m_copied;
static std::atomic_size_t m_destructed;
}; /* end class InstanceCounter */
// Compiler will make sure these static variables are defined only once.
template <class T> std::atomic_size_t InstanceCounter<T>::m_constructed = 0;
template <class T> std::atomic_size_t InstanceCounter<T>::m_copied = 0;
template <class T> std::atomic_size_t InstanceCounter<T>::m_destructed = 0;
struct Data
: public InstanceCounter<Data>
{
std::size_t buffer[1024];
}; /* end struct Data */
struct Data2
: public InstanceCounter<Data2>
{
Data2() = default;
Data2(Data2 const & other)
#if 0
// Don't forget to call the base class copy constructor. The implicit copy
// constructor calls it for you. But when you have custom copy
// constructor, if you do not specify the base constructor, the default
// constructor in the base class is used.
: InstanceCounter<Data2>(other)
#endif
{
std::copy_n(other.buffer, 1024, buffer);
}
Data2 & operator=(Data2 const & other)
{
std::copy_n(other.buffer, 1024, buffer);
return *this;
}
std::size_t buffer[1024];
}; /* end struct Data */
template <class T>
void report(std::string const & prefix)
{
using ct = InstanceCounter<T>;
std::cout
<< prefix
<< " instance: active = " << ct::active()
<< " constructed = " << ct::constructed()
<< " copied = " << ct::copied()
<< " destructed = " << ct::destructed()
<< std::endl;
}
int main(int argc, char ** argv)
{
std::cout << "** Creation phase **" << std::endl;
// Data.
Data * data = new Data();
report<Data> ("Data (default construction) ");
Data * data_copied = new Data(*data);
report<Data> ("Data (copy construction) ");
std::vector<Data> dvec(64);
report<Data> ("Data (construction in vector)");
// Data2.
Data2 * data2 = new Data2();
report<Data2>("Data2 (default construction) ");
Data2 * data2_copied = new Data2(*data2);
report<Data2>("Data2 (copy construction) ");
std::vector<Data2> d2vec(64);
report<Data2>("Data2 (construction in vector)");
std::cout << "** Deletion phase **" << std::endl;
// Data.
std::vector<Data>().swap(dvec);
report<Data>("Data ");
delete data;
report<Data>("Data ");
delete data_copied;
report<Data>("Data ");
// Data2.
std::vector<Data2>().swap(d2vec);
report<Data2>("Data2");
delete data2;
report<Data2>("Data2");
delete data2_copied;
report<Data2>("Data2");
return 0;
}
|