Dynamic Memory Allocation#

<mlib/alloc.h> (header file)#

Contains types, functions, and constants related to dynamic memory management.

C APIs#

Types#

struct mlib_allocator#

Provides support for customizing dynamic memory allocation.

Header:

mlib/alloc.h

Zero-initialized:

A zero-initialized mlib_allocator is not valid for any operation except with mlib_deallocate() of a null pointer.

Many amongoc APIs accept an mlib_allocator parameter, or otherwise consult other objects that carry an mlib_allocator (e.g. amongoc_loop has an associated allocator, and therefore any object associated with an event loop will also use that same allocator).

mlib_allocator_impl const *impl#

Pointer to the allocator implementation.

struct mlib_allocator_impl#

Provides the backing implementation for an mlib_allocator

void *userdata#

Arbitrary pointer to context for the allocator.

void *reallocate(
void *userdata,
void *[[nullable]] prev_ptr,
size_t requested_size,
size_t alignment,
size_t previous_size,
size_t *[[storage]] out_new_size,
)#

(Function pointer member)

Note

Don’t call this directly. Use mlib_allocate(), mlib_deallocate(), and mlib_reallocate()

Implements custom allocation for the allocator. The user must provide a non-null pointer to a function for the allocator.

Parameters:
  • userdata – The mlib_allocator_impl::userdata pointer.

  • prev_ptr – Pointer that was previously returned by reallocate()

  • requested_size – The requested amount of memory for the new region, or zero to request deallocation of prev_ptr.

  • alignment – The requested alignment of the new memory region. Must be a power of two.

  • previous_size – If prev_ptr is not nullptr, this is the previous requested_size used when prev_ptr was allocated.

  • out_new_size – An output parameter: The allocator will write the actually allocated size to this pointer. The argument may be nullptr.

Returns:

  • Upon success, must return a pointer to the newly allocated region of size at least requested_size with alignment of at least alignment.

  • Upon failure, must return nullptr.

Allocation Behavior

In brief:

  1. If requested_size is zero: Behave as-if free(prev_ptr), set *out_new_size to zero.

  2. Otherwise, behave as-if realloc(prev_ptr, requested_size), set *out_new_size to requested_size.

In greater detail, a user-provided allocation function must do the following:

  1. If prev_ptr is null:

    1. If requested_size is zero, return a null pointer.

    2. Attempt to allocate a region \(R\) of size \(S\) bytes, where \(S\) is at least requested_size.

    3. If allocating \(R\) fails, return a null pointer.

    4. If out_new_size is not null, write \(S\) to *out_new_size.

    5. Return a pointer to the beginning of \(R\).

  2. Otherwise (prev_ptr is non-null), let \(R_p\) be the existing memory region of size \(S_p\) pointed-to by prev_ptr.

  3. If requested_size is zero:

    1. Release the region \(R_p\).

    2. If out_new_size is not null, write zero to *out_new_size.

    3. Return a null pointer.

  4. Otherwise (requested_size is non-zero), if requested_size is greater than \(S_p\):

    1. Attempt to grow the region \(R_p\) to a new size \(S\) where \(S\) is at least requested_size bytes. If successful:

      1. If out_new_size is not null, write \(S\) to *out_new_size.

      2. Return a pointer to \(R_p\).

    2. Otherwise (growing the region failed), attempt to allocate a new region \(R\) of size \(S\), where \(S\) is at least requested_size bytes. If succesful:

      1. Copy the first \(S_p\) bytes from \(R_p\) into \(R\).

      2. Release the region \(R_p\).

      3. If out_new_size is not null, write \(S\) to *out_new_size.

      4. Return a pointer to the beginning of \(R\).

    3. Otherwise (allocating a new region failed), return a null pointer. (Do not modify the region \(R_p\) nor write anything to out_new_size)

  5. Otherwise (requested_size is non-zero and at most \(S_p\)):

    1. Optionally, to simply reuse the region \(R_p\):

      1. If out_new_size is not null, write \(S_p\) to *out_new_size

      2. Return prev_ptr unmodified.

    2. Otherwise, attempt to shrink the region \(R_p\) to a new size \(S\) that is at least requested_size bytes. If successful:

      1. If out_new_size is not null, write \(S\) to *out_new_size.

      2. Return a pointer to \(R_p\).

    3. Otherwise (shrinking the region failed), attempt to allocate a new region \(R\) of size \(S\), where \(S\) is at least requested_size. If succesful:

      1. Copy the first requested_size bytes from \(R_p\) into \(R\).

      2. If out_new_size is not null, write \(S\) into *out_new_size.

      3. Release the region \(R_p\).

      4. Return a pointer to the beginning of \(R\).

    4. Otherwise (allocating a new region failed), return a null pointer (Do not modify the region \(R_p\) nor write anything to out_new_size).

Functions#

void *mlib_allocate(mlib_allocator alloc, size_t sz)#
void mlib_deallocate(mlib_allocator alloc, void *p, size_t sz)#
void *mlib_reallocate(
mlib_allocator alloc,
void *prev_ptr,
size_t sz,
size_t alignment,
size_t prev_size,
size_t *out_new_size,
)#

Attempt to allocate or deallocate memory using the allocator alloc.

Parameters:
  • alloc – The allocator to be used.

  • p – (For deallocation) A pointer that was previously returned by mlib_allocate() using the same alloc parameter.

  • sz – For allocation, the requested size. For deallocation, this must be the original sz value that was used with mlib_allocate().

Returns:

  • For allocation functions: upon success: returns a pointer to the beginning of a newly allocated region of at least size sz and optional alignment alignment. Upon failure, returns nullptr.

Header:

mlib/alloc.h

The mlib_reallocate() function is a wrapper around the mlib_allocator_impl::reallocate() function.

Important

For mlib_deallocate(): If the pointer p is null, then it is legal for alloc to have a null mlib_allocator::impl member. This is the only legal use of a null allocator.

This behavior is crafted specifically to support deletion of aggregate types which store an allocator and a pointer as a member, so that deleting a zero-initialized instance of that struct is a valid no-op.

Constants#

const mlib_allocator mlib_default_allocator#

A reasonable default mlib_allocator.

Header:

mlib/alloc.h

This allocator is implemented in terms of the standard library realloc() and free() functions.

const mlib_allocator mlib_terminating_allocator#

A special mlib_allocator that terminates the program if there is any attempt to allocate memory through it.

Header:

mlib/alloc.h

This allocator is intended to be used in places where the programmer wishes to assert that dynamic allocation will not occur. If an attempt is made to allocate memory using this alloator, then a diagnostic will be printed to standard error and abort() will be called.

C++ APIs#

Types#

template<typename T = void>
class mlib::allocator#

Provides a C++ allocator interface for an mlib_allocator

Header:

mlib/alloc.h

This allocator type is not default-constructible: It must be constructed explicitly from an mlib_allocator.

using value_type = T#
using pointer = value_type*#

Types associated with this allocator.

Note

If T is void, then the allocator is a proto-allocator and must be converted to a typed allocator before it may be used.

allocator(mlib_allocator a)#

Convert from an mlib_allocator a.

template<typename U>
allocator(allocator<U>)#

Convert-construct from another allocator instance, rebinding the allocated type.

bool operator==(allocator) const#

Compare two allocators. Two allocators are equal if the mlib_allocator_impl::userdata and mlib_allocator_impl::reallocate() pointers are equal.

mlib_allocator c_allocator() const#

Obtain the mlib_allocator that is used by this allocator

pointer allocate(std::size_t n) const#
void deallocate(pointer p, std::size_t n) const#

The allocation/deallocation functions for the C++ allocator interface.

Parameters:
  • n – The number of objects to be allocated/deallocated

  • p – Pointer to a previous region obtained from an equivalent allocator

Calls mlib_allocate()/mlib_deallocate() to perform the allocation.

template<typename ...Args>
pointer new_(Args&&...) const#
void delete_(pointer p) const#

New/delete individual objects using the allocator.

template<typename U>
allocator<U> rebind() const#

Rebind the type parameter for the allocator.

template<typename ...Args>
void construct(pointer p, Args&&... args) const#

Construct an object at p with uses-allocator construction. This will “inject” the allocator into objects that support construction using the same memory allocator. This allows the following to work properly:

// An allocator to be used
mlib::allocator<> a = get_some_allocator();
// A string type that uses an mlib allocator
using string = std::basic_string<char, std::char_traits<char>, mlib::allocator<char>>
// Construct a vector with our allocator
std::vector<string, mlib::allocator<string>> strings{a};
// Append a new string
strings.emplace_back("hello, world!");  // [note]

On the line marked [note] we are emplace-constructing a string from a character array. This would not work if the construct() method was not available, as the vector would try to default-construct a new mlib::allocator, which is not allowed. In this example, emplace_back will end up calling allocator::construct() for the string, which will inject the parent allocator into the string during construction.

template<typename Alloc, typename T>
class mlib::bind_allocator#

Create an object with a bound allocator.

Header:

mlib/alloc.h

This class type supports CTAD, and using CTAD is recommended. It will perfect-forward the bound object into the resulting bind_allocator wrapper.

bind_allocator(Alloc a, T &&obj)#

Bind the allocator a to the object obj

using allocator_type = Alloc#
allocator_type get_allocator() const#

Return the allocate that was bound with this object

decltype(auto) operator()(auto&&...)#

Call the underlying invocable with the given arguments, if such a call is well-formed.

This method is cvref-overloaded for the underlying object.

auto query(auto q) const#

Apply a query to the underlying object. (See: Query Objects)

struct mlib::alloc_deleter#

A deleter type for use with std::unique_ptr that deletes an object using an mlib::allocator

Header:

mlib/alloc.h

template<typename T>
using mlib::unique_ptr = std::unique_ptr<T, alloc_deleter>#

A std::unique_ptr type that uses an mlib::allocator.

Header:

mlib/alloc.h

Functions#

template<typename T>
unique_ptr<T> mlib::allocate_unique(
allocator<> a,
auto&&... args,
)#

Construct an mlib::unique_ptr<T> using the given allocator to manage the object.

Header:

mlib/alloc.h

Constants#

const allocator<> mlib::terminating_allocator{::mlib_terminating_allocator}#

A C++ version of the mlib_terminating_allocator

Header:

mlib/alloc.h