Add overalignment support for new_delete_resource for newer (__cpp_aligned_new) and older standards, including some workaround for targets where __STDCPP_DEFAULT_NEW_ALIGNMENT__ value is not correctly aligned between GCC and the malloc implementation (Win32)

This commit is contained in:
Ion Gaztañaga
2025-12-26 10:59:37 +01:00
parent 52b34fb1da
commit ae9265dc7d
3 changed files with 206 additions and 20 deletions

View File

@@ -1451,8 +1451,9 @@ use [*Boost.Container]? There are several reasons for that:
* Implemented C++20's [funcref boost::container::uninitialized_construct_using_allocator uninitialized_construct_using_allocator]
and [funcref boost::container::make_obj_using_allocator make_obj_using_allocator].
* Added `[[nodiscard]]` to several allocator and PMR utilities.
* Implemented overaligned operator new/delete support for `new_allocator` and `pmr::new_delete_resource()`
when C++17's `cpp_aligned_new` is available.
* Implemented overaligned operator new/delete support for `new_allocator` and `pmr::new_delete_resource()`:
* Uses C++17's `cpp_aligned_new` if available.
* Uses alternative aligned allocation functions for Win32/Unix otherwise.
* Fixed bugs/issues:
* [@https://github.com/boostorg/container/issues/323 GitHub #323: ['"flat_tree::try_emplace UB"]].

View File

@@ -0,0 +1,152 @@
//////////////////////////////////////////////////////////////////////////////
//
// (C) Copyright Ion Gaztanaga 2025-2025. Distributed under the Boost
// Software License, Version 1.0. (See accompanying file
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// See http://www.boost.org/libs/container for documentation.
//
//////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_CONTAINER_DETAIL_ALIGNED_ALLOC_HPP
#define BOOST_CONTAINER_DETAIL_ALIGNED_ALLOC_HPP
#ifndef BOOST_CONFIG_HPP
# include <boost/config.hpp>
#endif
#if defined(BOOST_HAS_PRAGMA_ONCE)
# pragma once
#endif
// Platform detection
#if defined(_WIN32) && !defined(__CYGWIN__)
#define BOOST_CONTAINER_HAS_ALIGNED_MALLOC
#else
#include <unistd.h> //Include it to detect POSIX features
#if defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)
#define BOOST_CONTAINER_HAS_POSIX_MEMALIGN
#elif defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE >= 600)
#define BOOST_CONTAINER_HAS_POSIX_MEMALIGN
#elif defined(__APPLE__)
#include <Availability.h>
#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500
#define BOOST_CONTAINER_HAS_ALIGNED_ALLOC
#else
#define BOOST_CONTAINER_HAS_POSIX_MEMALIGN
#endif
#elif defined(__ANDROID__)
#if (__ANDROID_API__ >= 28)
#define BOOST_CONTAINER_HAS_ALIGNED_ALLOC
#else
#define BOOST_CONTAINER_HAS_POSIX_MEMALIGN
#endif
#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L)
#define BOOST_CONTAINER_HAS_ALIGNED_ALLOC
#endif
#endif
// Include
#if defined(BOOST_CONTAINER_HAS_ALIGNED_MALLOC)
#include <malloc.h>
#elif defined(BOOST_CONTAINER_HAS_POSIX_MEMALIGN)
#include <stdlib.h>
#elif defined(BOOST_CONTAINER_HAS_ALIGNED_ALLOC)
#include <stdlib.h>
#else
#include <stdlib.h> //for malloc
#endif
namespace boost {
namespace container {
namespace dtl {
#if defined(BOOST_CONTAINER_HAS_POSIX_MEMALIGN)
inline void* aligned_allocate(std::size_t al, std::size_t sz)
{
void *ptr;
// posix_memalign requires aligned multiple of void*
if (al < sizeof(void*))
al = sizeof(void*);
int ret = posix_memalign(&ptr, al, sz);
if (ret != 0)
return 0;
return ptr;
}
#elif defined(BOOST_CONTAINER_HAS_ALIGNED_ALLOC)
inline void* aligned_allocate(std::size_t al, std::size_t sz)
{
// Some aligned_allocate are based on posix_memalign so require also minimal alignment
if (al < sizeof(void*))
al = sizeof(void*);
// aligned_allocate requires size to be a multiple of alignment
std::size_t rounded_size = std::size_t(sz + al - 1u) & ~std::size_t(al - 1);
//Check for rounded size overflow
return rounded_size ? aligned_allocate(al, rounded_size) : 0;
}
#elif defined(BOOST_CONTAINER_HAS_ALIGNED_MALLOC)
inline void* aligned_allocate(std::size_t al, std::size_t sz)
{
return _aligned_malloc(sz, al);
}
#else
inline void* aligned_allocate(std::size_t al, std::size_t sz)
{
//Make room for a back pointer metadata
void* const mptr = malloc(sz + sizeof(void*) + al);
if (!mptr)
return 0;
//Now align the returned pointer (which will be aligned at least to sizeof(void*)
std::size_t raw_addr = reinterpret_cast<std::size_t>(mptr);
std::size_t offset = sizeof(void*);
void *const ptr = reinterpret_cast<void*>((raw_addr + offset + al - 1u) & ~(al - 1u));
// Store the original pointer just before the aligned address
void** backpointer = reinterpret_cast<void**>(ptr) - 1;
*backpointer = mptr;
return ptr;
}
#endif
#if defined(BOOST_CONTAINER_HAS_ALIGNED_ALLOC) || defined(BOOST_CONTAINER_HAS_POSIX_MEMALIGN)
inline void aligned_deallocate(void* ptr)
{
if (!ptr)
return;
free(ptr);
}
#elif defined(BOOST_CONTAINER_HAS_ALIGNED_MALLOC)
inline void aligned_deallocate(void* ptr)
{
_aligned_free(ptr); //_aligned_free supports NULL ptr
}
#else
inline void aligned_deallocate(void* ptr)
{
// Obtain backpointer data and free it
void** storage = reinterpret_cast<void**>(ptr) - 1;
free(*storage);
}
#endif//
} //namespace dtl {
} //namespace container {
} //namespace boost {
#endif //#ifndef BOOST_CONTAINER_DETAIL_ALIGNED_ALLOC_HPP

View File

@@ -22,19 +22,50 @@
#include <boost/container/throw_exception.hpp>
#include <boost/container/detail/type_traits.hpp>
#if !defined(__cpp_aligned_new)
#include <boost/container/detail/aligned_alloc.hpp>
#endif
namespace boost {
namespace container {
namespace dtl {
BOOST_CONTAINER_FORCEINLINE bool operator_new_raw_overaligned(std::size_t alignment)
{
//GCC-clang on Mingw-w64 has problems with malloc (MSVCRT / UCRT) alignment not matching
//__STDCPP_DEFAULT_NEW_ALIGNMENT__, since HeapAlloc alignment is 8 for 32 bit targets
#if !defined(__cpp_aligned_new) || (defined(_WIN32) && !defined(_WIN64) && !defined(_MSC_VER))
return alignment > 2u*sizeof(void*);
#else
return alignment > __STDCPP_DEFAULT_NEW_ALIGNMENT__;
#endif
}
BOOST_CONTAINER_FORCEINLINE bool operator_new_raw_overaligned_tricky()
{
//GCC-clang on Mingw-w64 has problems with malloc (MSVCRT / UCRT) alignment not matching
//__STDCPP_DEFAULT_NEW_ALIGNMENT__, since HeapAlloc alignment is 8 for 32 bit targets
#if !defined(__cpp_aligned_new) || (defined(_WIN32) && !defined(_WIN64) && !defined(_MSC_VER))
return true;
#else
return false;
#endif
}
BOOST_CONTAINER_FORCEINLINE void* operator_new_raw_allocate(const std::size_t size, const std::size_t alignment)
{
(void)alignment;
#if defined(__cpp_aligned_new)
if(__STDCPP_DEFAULT_NEW_ALIGNMENT__ < alignment) {
if(operator_new_raw_overaligned(alignment)) {
#if defined(__cpp_aligned_new)
return ::operator new(size, std::align_val_t(alignment));
#else
//C++ requires zero-sized allocations to return a non-null pointer
return aligned_allocate(alignment, !size ? 1 : size);
#endif
}
else{
return ::operator new(size);
}
#endif
return ::operator new(size);
}
BOOST_CONTAINER_FORCEINLINE void operator_delete_raw_deallocate
@@ -42,22 +73,24 @@ BOOST_CONTAINER_FORCEINLINE void operator_delete_raw_deallocate
{
(void)size;
(void)alignment;
#ifdef __cpp_aligned_new
if(__STDCPP_DEFAULT_NEW_ALIGNMENT__ < alignment) {
# if defined(__cpp_sized_deallocation)
::operator delete(ptr, size, std::align_val_t(alignment));
if(operator_new_raw_overaligned(alignment)) {
#if defined(__cpp_aligned_new)
# if defined(__cpp_sized_deallocation)
::operator delete(ptr, size, std::align_val_t(alignment));
#else
::operator delete(ptr, std::align_val_t(alignment));
# endif
#else
::operator delete(ptr, std::align_val_t(alignment));
# endif
return;
aligned_deallocate(ptr);
#endif
}
else {
# if defined(__cpp_sized_deallocation)
::operator delete(ptr, size);
#else
::operator delete(ptr);
# endif
}
#endif
# if defined(__cpp_sized_deallocation)
::operator delete(ptr, size);
#else
::operator delete(ptr);
# endif
}
template <class T>