The vulnerability as expressed in ISO/IEC TR 24772-1:2019 and ISO/IEC TR 24772-3:2020 C exists in C++. C++, however, provides mechanisms to mitigate the vulnerability. In contrast to C, where the mere existance of reachable memory for an object is sufficient to access it, the lifetime model of C++ makes it undefined behaviour [EWF] to access an object outside of its lifetime. This results in undefined behavior, when an object access is attempted before one of its constructors is finished or after its destruction. For example, container types like std::vector
or wrapper types like std::optional
might have memory for an object available, that is not constructed or has ended its lifetime. For similar situations that result from accessing temporary objects or variables outside of their lifetime see subclause [DCM]. If such a temporary or local object manages heap memory (e.g., std::vector
) referring to an element after the manager’s lifetime ended technically falls into the category of this vulnerability, but is covered there.
C++ provides a rich set of pointer-like types (potentially referring to heap memory) whose values may dangle, e.g.,
std::string_view
std::span
std::reference_wrapper
In addition, a user-defined class type can be a pointer-like type, if a subobject is of pointer-like type and refers to an object (target) whose lifetime is different from and not managed by the current object. Sometimes, regular object types act as pointer-like types, e.g., indices into a container or operating system handles, and their validity can not be directly mapped to the C++ object lifetime model.
If the lifetime of a pointer-like value ends before the lifetime of its target, then the vulnerability does not apply to that pointer-like value. This is the primary C++ strategy for avoiding vulnerabilities of dangling pointer-like values. For example, an object argument passed as a function parameter of reference type persists throughout the function call. The lifetime guarantee of a function argument passed indirectly via a pointer-like type does not apply if * the target is destroyed explicitly by the called function (taking ownership of the target) or a concurrently executing operation, or if * copies of the pointer-like parameter outlive the function call, for example, as the return value, or in a coroutine or thread frame.
For objects directly allocated on the heap C++ provides smart pointers and corresponding factory functions (e.g., std::make_unique()
) that allow transferring ownership or shared ownership to reduce the risk for dangling. However, storing the raw pointers managed by smart pointers can lead to accidental dangling, for example:
int * f(){
auto up = std::make_unique<int>(42);
return up.get(); // returned pointer dangles
}
The C++ library containers, such as std::vector
, manage the required heap memory for their elements. Referring to an element in a container via a pointer-like type is safe, as long as the container remains unchanged while the element object is accessed. In general, accessing an element in a mutated container via a pointer-like value obtained before the mutation is undefined behavior [EWF]. Different containers provide different validity guarantees of accessing an element via a pointer-like type that was obtained before a subsequent change in that container.
Hand-written loops are prone to attempt to access elements of a container that are non-existent, or have been relocated. Employing standard library algorithms to iterate over a range of elements from a container tends to be safer, as long as the underlying container is not accidentally changed. For example, the following code can cause a failure, due to the attempt to iterate over a changing std::vector
:
std::vector v{1,2,3,4};
// modifying v while iterating is undefined behaviour
copy(begin(v),end(v),back_inserter(v)); std::ostream_iterator<int>(std::cout,", ")); copy(begin(v),end(v),
A C++-specific way of causing dangling_references to the heap is by means of the placement_new
construct (see clause 7.4).
To avoid the vulnerability or mitigate its ill effects, C++ software developers can:
Use the avoidance mechanisms of ISO/IEC 24772-1 clause 6.14.5.
Prefer value types that manage heap memory, for example, std::vector
or std::string
, and pass by value/return by value over the use of pointer-like types.
If value types are not feasible, adopt a style that makes explicit the ownership and lifetime of heap resources, by using std::unique_ptr
or std::shared_ptr
or similar manager types and allocate heap memory exclusively with their corresponding factory functions std::make_unique/std::make_shared
.
Ensure all copies of any pointer-like value are no longer accessible or accessed after the referent’s lifetime ends.
Prohibit access of a container through an invalidated iterator, and similarly, access via other potentially dangling pointer-like values, e.g., views.
Avoid unmanaged heap allocation by new
/delete
expressions or calling C-library heap allocation functions.
Avoid manually ending the lifetime of an non-owned object by explicitly calling its destructor or std::destroy_at
.
Use static and dynamic analysis tools to detect dangling.