Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

Allocation and deallocation functions can be overloaded at both global and class scope.

If an allocation function is overloaded in a given scope, the corresponding deallocation function should also be overloaded in the same scope.

Failure to overload the corresponding dynamic storage function is likely to violate rules like VOID MEM39-CPP. Resources allocated by memory allocation functions must be released using the corresponding memory deallocation function. For instance, if an overloaded allocation function uses a private heap to perform its allocations, passing a pointer returned by it to the default deallocation function will likely cause undefined behavior. Even in situations where the allocation function ultimately calls through to the default allocator to obtain a pointer to memory, failing to overload a corresponding deallocation function may leave the program in an unexpected state by not updating internal allocator state.

Noncompliant Code Example

In this noncompliant code example, an allocation function is overloaded at global scope, however, the corresponding deallocation function is not declared. Were an object to be allocated with the overloaded allocation function, any attempt to delete the object would result in undefined behavior in violation of VOID If you overload operator new in global scope, you should also overload operator delete in global scope. Likewise, overloading operator new in the scope of a class mandates the overloading of operator delete in the same class. Failure to overload operator delete will cause the global delete to be called to release a resource that was allocated with an overloaded operator new. Violation of this rule is likely to also violate MEM39-CPP. Resources allocated by memory allocation functions must be released using the corresponding memory deallocation function.

Code Block
bgColor#FFcccc
langcpp
#include <Windows.h>
#include <new>
 
void *operator new(std::size_t size) noexcept(false) {
  static HANDLE H = ::HeapCreate(0, 0, 0); // Private, expandable heap
  if (H) {
    return ::HeapAlloc(H, 0, size);
  }
  throw std::bad_alloc();
}

Compliant Solution

In this compliant solution, the corresponding deallocation function is also defined at global scope:

Code Block
bgColor#ccccff
langcpp
#include <Windows.h>
#include <new>

class HeapAllocator {
  static HANDLE H;
  static bool Init;
 
public:
  static void *alloc(std::size_t size) noexcept(false) {
    if (!Init) {
      H = ::HeapCreate(0, 0, 0); // Private, expandable heap
      Init = true;
    }
 
    if (H) {
      return ::HeapAlloc(H, 0, size);
    }
    throw std::bad_alloc();
  }
 
  static void dealloc(void *ptr) noexcept {
    if (H) {
      (void)::HeapFree(H, 0, ptr);
    }
  }
};
 
HANDLE HeapAllocator::H = nullptr;
bool HeapAllocator::Init = false;

void *operator new(std::size_t size) noexcept(false) {
  return HeapAllocator::alloc(size);
}
 
void operator delete(void *ptr) noexcept {
  return HeapAllocator::dealloc(ptr);
}

Noncompliant Code Example

In this noncompliant code example, operator new() is overloaded at class scope, but operator delete() is not similarly overloaded at class scope. Despite the fact that the overloaded allocation function calls through to the default global allocation function, were an object of type S to be allocated, any attempt to delete the object would result in leaving the program in an indeterminate state due to failing to update allocation bookkeeping accordingly.

Code Block
bgColor#FFcccc
langcpp
#include <new>
 
extern "C++" void update_bookkeeping(void *allocated_ptr, std::size_t size, bool alloc);
 
struct S {
  void *operator new(std::size_t size) noexcept(false) {
    void *ptr = ::operator new(size);
    update_bookkeeping(ptr, size, true);
    return ptr;
  }
};

Compliant Solution

In this compliant solution, the corresponding operator delete() is overloaded at the same class scope:

Code Block
bgColor#ccccff
langcpp
#include <new>

extern "C++" void update_bookkeeping(void *allocated_ptr, std::size_t size, bool alloc);

struct S {
  void *operator new(std::size_t size) noexcept(false) {
    void *ptr = ::operator new(size);
    update_bookkeeping(ptr, size, true);
    return ptr;
  }
 
  void operator delete(void *ptr, std::size_t size) noexcept {
    ::operator delete(ptr);
    update_bookkeeping(ptr, size, false);
  }
};

Exceptions

DCL35-EX1: A placement deallocation function may be elided for a corresponding placement allocation function, but only in instances where the object placement allocation and object construction are guaranteed to be noexcept(true). Since placement deallocation functions are only called when some part of the object initialization terminates by throwing an exception, it is safe to elide the placement deallocation function when exceptions cannot be thrown. For instance, some vendors implement compiler flags disabling exception support (such as -fno-cxx-exceptions in Clang or /EHs-c- in Microsoft Visual Studio), which has implementation-defined behavior when an exception is thrown, but generally results in program termination similar to calling abort().

Risk Assessment

Mismatched usage of new and delete could lead to a denial-of-service attack.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

DCL35-CPP

Low

UnlikelyProbable

Low

P3P6

L3L2

Automated Detection

  

Tool

Version

Checker

Description

PRQA QA-C++
Include Page
PRQA QA-C++_v
PRQA QA-C++_v
2160,2161 

 

Bibliography

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

Related Guidelines

Bibliography

[ISO/IEC 14882-2014]

3.7.4, "Dynamic Storage Duration"
5.3.4, "New"
5.3.5, "Delete" 

...