LLCPP Memory Ownership

This document provides an overview of the tools available to manage memory when using the LLCPP bindings.

Pointers and memory ownership

LLCPP objects use special smart pointers called tracking_ptr to keep track of memory ownership. With tracking_ptr, LLCPP makes it possible for your code to easily set a value and forget about ownership since tracking_ptr will take care of freeing memory when it goes out of scope.

These pointers have two states:

  • Unowned (constructed from an unowned_ptr_t).
  • Heap allocated and owned (constructed from a std::unique_ptr).

When the contents is owned, a tracking_ptr behaves like a unique_ptr and the pointer is deleted on destruction. In the unowned state, tracking_ptr behaves like a raw pointer and destruction is a no-op.

tracking_ptr is move-only and has an API closely matching unique_ptr.

Types of object allocation

tracking_ptr makes it possible to create LLCPP objects with several allocation strategies. The allocation strategies can be mixed and matched within the same code.

Heap allocation

To heap allocate objects, use the standard std::make_unique.

An example with an optional uint32 field represented as a tracking_ptr.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="heap-field" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

This applies to all union and table fields and data arrays within vectors and strings. Vector and string data arrays must use the array specialization of std::unique_ptr, which takes the element count as an argument.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="heap-vec" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

To copy a collection to a VectorView, use heap_copy_vec.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="heap-copy-vec" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

To copy a string to a StringView, use heap_copy_str.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="heap-copy-str" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Allocators

FIDL provides an Allocator API that enables creating tracking_ptrs to LLCPP objects through a number of allocation algorithms. Currently, BufferThenHeapAllocator, UnsafeBufferAllocator, and HeapAllocator are available in fidl namespace.

The BufferThenHeapAllocator allocates from an in-band fixed-size buffer (can be used for stack allocation), but falls back to heap allocation if the in-band buffer has been exhausted (to avoid unnecessary unfortunate surprises). Be aware that excessive stack usage can cause its own problems, so consider using a buffer size that comfortably fits on the stack, or consider putting the whole BufferThenHeapAllocator on the heap if the buffer needs to be larger than fits on the stack, or consider using HeapAllocator. Allocations must be assumed to be gone upon destruction of the BufferThenHeapAllocator used to make them.

The HeapAllocator always allocates from the heap, and is unique among allocators (so far) in that all of the HeapAllocator allocations can out-live the HeapAllocator instance used to make them.

The UnsafeBufferAllocator is unsafe in the sense that it lacks heap failover, so risks creating unfortunate data-dependent surprises unless the buffer size is absolutely guaranteed to be large enough including the internal destructor-tracking overhead. If the internal buffer is exhausted, make<>() will panic the entire process. Consider using BufferThenHeapAllocator instead. Do not use UnsafeBufferAllocator without rigorously testing that the worst-case set of cumulative allocations made through the allocator all fit without a panic, and consider how the rigor will be maintained as code and FIDL tables are changed.

Example:

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="allocator-field" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

The arguments to allocator.make are identical to the arguments to std::make_unique. This also applies to VectorViews.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="allocator-vec" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

To copy a collection to a VectorView using an allocator, use copy_vec.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="copy-vec" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

To create a copy of a string using an allocator, use copy_str.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="copy-str" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

Unowned pointers

In addition to the managed allocation strategies, it is also possible to directly create pointers to memory unowned by FIDL. This is discouraged, as it is easy to accidentally create use-after-free bugs. unowned_ptr exists to explicitly mark pointers to FIDL-unowned memory.

The unowned_ptr helper is the recommended way to create unowned_ptr_ts, which is more ergonomic than using the unowned_ptr_t constructor directly.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="unowned-ptr" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

To create a VectorView from a collection using an unowned pointer to the collection's data array, use unowned_vec.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="unowned-vec" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

To create a StringView from unowned memory, use unowned_str.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="unowned-str" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

A StringView can also be created directly from string literals without using unowned_ptr.

{%includecode gerrit_repo="fuchsia/fuchsia" gerrit_path="examples/fidl/llcpp/unittests/main.cc" region_tag="stringview-assign" adjust_indentation="auto" exclude_regexp="^TEST|^}" %}

fidl::StringView

Defined in lib/fidl/llcpp/string_view.h

Holds a reference to a variable-length string stored within the buffer. C++ wrapper of fidl_string. Does not own the memory of the contents.

fidl::StringView may be constructed by supplying the pointer and number of UTF-8 bytes (excluding trailing \0) separately. Alternatively, one could pass a C++ string literal, or any value which implements [const] char* data() and size(). The string view would borrow the contents of the container.

It is memory layout compatible with fidl_string.

fidl::VectorView<T>

Defined in lib/fidl/llcpp/vector_view.h

Holds a reference to a variable-length vector of elements stored within the buffer. C++ wrapper of fidl_vector. Does not own the memory of elements.

fidl::VectorView may be constructed by supplying the pointer and number of elements separately. Alternatively, one could pass any value which supports std::data, such as a standard container, or an array. The vector view would borrow the contents of the container.

It is memory layout compatible with fidl_vector.

fidl::Array<T, N>

Defined in lib/fidl/llcpp/array.h

Owns a fixed-length array of elements. Similar to std::array<T, N> but intended purely for in-place use.

It is memory layout compatible with FIDL arrays, and is standard-layout. The destructor closes handles if applicable e.g. it is an array of handles.