Update realloc(p,0) semantics. (#753)

* Factor out small sizeclass check

* Update realloc(p,0) semantics

This commit changes the behaviour of realloc(p,0) to be free(p) if p!=nullptr, and malloc(0) if p== nullptr.
This commit is contained in:
Matthew Parkinson
2025-02-27 15:20:31 +00:00
committed by GitHub
parent 6622dc584e
commit 06df9dd9e1
3 changed files with 55 additions and 23 deletions

View File

@@ -52,6 +52,25 @@ namespace snmalloc::libc
SNMALLOC_FAST_PATH_INLINE void* realloc(void* ptr, size_t size)
{
// Glibc treats
// realloc(p, 0) as free(p)
// realloc(nullptr, s) as malloc(s)
// and for the overlap
// realloc(nullptr, 0) as malloc(1)
// Without the first two we don't pass the glibc tests.
// The last one is required by various gnu utilities such as grep, sed.
if (SNMALLOC_UNLIKELY(!is_small_sizeclass(size)))
{
if (SNMALLOC_UNLIKELY(size == 0))
{
if (SNMALLOC_UNLIKELY(ptr == nullptr))
return malloc(1);
dealloc(ptr);
return nullptr;
}
}
size_t sz = alloc_size(ptr);
// Keep the current allocation if the given size is in the same sizeclass.
if (sz == round_size(size))
@@ -72,13 +91,10 @@ namespace snmalloc::libc
}
dealloc(ptr);
}
else if (SNMALLOC_LIKELY(size == 0))
{
dealloc(ptr);
}
else
{
return set_error();
// Error should be set by alloc on this path already.
SNMALLOC_ASSERT(errno == ENOMEM);
}
return p;
}

View File

@@ -457,6 +457,17 @@ namespace snmalloc
constexpr SizeClassLookup sizeclass_lookup = SizeClassLookup();
/**
* @brief Returns true if the size is a small sizeclass. Note that
* 0 is not considered a small sizeclass.
*/
constexpr bool is_small_sizeclass(size_t size)
{
// Perform the - 1 on size, so that zero wraps around and ends up on
// slow path.
return (size - 1) < sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1);
}
constexpr smallsizeclass_t size_to_sizeclass(size_t size)
{
auto index = sizeclass_lookup_index(size);
@@ -482,7 +493,7 @@ namespace snmalloc
*/
static inline sizeclass_t size_to_sizeclass_full(size_t size)
{
if ((size - 1) < sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1))
if (is_small_sizeclass(size))
{
return sizeclass_t::from_small_class(size_to_sizeclass(size));
}
@@ -493,26 +504,28 @@ namespace snmalloc
inline SNMALLOC_FAST_PATH static size_t round_size(size_t size)
{
if (size > sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1))
if (is_small_sizeclass(size))
{
if (size > bits::one_at_bit(bits::BITS - 1))
{
// This size is too large, no rounding should occur as will result in a
// failed allocation later.
return size;
}
return bits::next_pow2(size);
return sizeclass_to_size(size_to_sizeclass(size));
}
// If realloc(ptr, 0) returns nullptr, some consumers treat this as a
// reallocation failure and abort. To avoid this, we round up the size of
// requested allocations to the smallest size class. This can be changed
// on any platform that's happy to return nullptr from realloc(ptr,0) and
// should eventually become a configuration option.
if (size == 0)
{
// If realloc(ptr, 0) returns nullptr, some consumers treat this as a
// reallocation failure and abort. To avoid this, we round up the size of
// requested allocations to the smallest size class. This can be changed
// on any platform that's happy to return nullptr from realloc(ptr,0) and
// should eventually become a configuration option.
return sizeclass_to_size(size_to_sizeclass(1));
}
return sizeclass_to_size(size_to_sizeclass(size));
if (size > bits::one_at_bit(bits::BITS - 1))
{
// This size is too large, no rounding should occur as will result in a
// failed allocation later.
return size;
}
return bits::next_pow2(size);
}
/// Returns the alignment that this size naturally has, that is
@@ -549,9 +562,7 @@ namespace snmalloc
// sizeclass calculation. We use the same fast path constant to
// move the case where result==0 to the slow path, and then check for which
// case we are in.
constexpr size_t SmallSizeClassUpperBound =
sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1) - 1;
if (SNMALLOC_LIKELY((result - 1) < SmallSizeClassUpperBound))
if (is_small_sizeclass(result))
return result;
// We are in the slow path, so we need to check for overflow.

View File

@@ -265,6 +265,9 @@ int main(int argc, char** argv)
test_calloc(0, size, SUCCESS, false);
}
// Check realloc(nullptr,0) behaves like malloc(1)
test_realloc(nullptr, 0, SUCCESS, false);
for (smallsizeclass_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++)
{
const size_t size = sizeclass_to_size(sc);
@@ -277,6 +280,8 @@ int main(int argc, char** argv)
test_realloc(our_malloc(size), size2, SUCCESS, false);
test_realloc(our_malloc(size + 1), size2, SUCCESS, false);
}
// Check realloc(p,0), behaves like free(p), if p != nullptr
test_realloc(our_malloc(size), 0, SUCCESS, true);
}
for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++)