Skip to content

Commit 06df9dd

Browse files
authored
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.
1 parent 6622dc5 commit 06df9dd

File tree

3 files changed

+55
-23
lines changed

3 files changed

+55
-23
lines changed

src/snmalloc/global/libc.h

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,25 @@ namespace snmalloc::libc
5252

5353
SNMALLOC_FAST_PATH_INLINE void* realloc(void* ptr, size_t size)
5454
{
55+
// Glibc treats
56+
// realloc(p, 0) as free(p)
57+
// realloc(nullptr, s) as malloc(s)
58+
// and for the overlap
59+
// realloc(nullptr, 0) as malloc(1)
60+
// Without the first two we don't pass the glibc tests.
61+
// The last one is required by various gnu utilities such as grep, sed.
62+
if (SNMALLOC_UNLIKELY(!is_small_sizeclass(size)))
63+
{
64+
if (SNMALLOC_UNLIKELY(size == 0))
65+
{
66+
if (SNMALLOC_UNLIKELY(ptr == nullptr))
67+
return malloc(1);
68+
69+
dealloc(ptr);
70+
return nullptr;
71+
}
72+
}
73+
5574
size_t sz = alloc_size(ptr);
5675
// Keep the current allocation if the given size is in the same sizeclass.
5776
if (sz == round_size(size))
@@ -72,13 +91,10 @@ namespace snmalloc::libc
7291
}
7392
dealloc(ptr);
7493
}
75-
else if (SNMALLOC_LIKELY(size == 0))
76-
{
77-
dealloc(ptr);
78-
}
7994
else
8095
{
81-
return set_error();
96+
// Error should be set by alloc on this path already.
97+
SNMALLOC_ASSERT(errno == ENOMEM);
8298
}
8399
return p;
84100
}

src/snmalloc/mem/sizeclasstable.h

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,17 @@ namespace snmalloc
457457

458458
constexpr SizeClassLookup sizeclass_lookup = SizeClassLookup();
459459

460+
/**
461+
* @brief Returns true if the size is a small sizeclass. Note that
462+
* 0 is not considered a small sizeclass.
463+
*/
464+
constexpr bool is_small_sizeclass(size_t size)
465+
{
466+
// Perform the - 1 on size, so that zero wraps around and ends up on
467+
// slow path.
468+
return (size - 1) < sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1);
469+
}
470+
460471
constexpr smallsizeclass_t size_to_sizeclass(size_t size)
461472
{
462473
auto index = sizeclass_lookup_index(size);
@@ -482,7 +493,7 @@ namespace snmalloc
482493
*/
483494
static inline sizeclass_t size_to_sizeclass_full(size_t size)
484495
{
485-
if ((size - 1) < sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1))
496+
if (is_small_sizeclass(size))
486497
{
487498
return sizeclass_t::from_small_class(size_to_sizeclass(size));
488499
}
@@ -493,26 +504,28 @@ namespace snmalloc
493504

494505
inline SNMALLOC_FAST_PATH static size_t round_size(size_t size)
495506
{
496-
if (size > sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1))
507+
if (is_small_sizeclass(size))
497508
{
498-
if (size > bits::one_at_bit(bits::BITS - 1))
499-
{
500-
// This size is too large, no rounding should occur as will result in a
501-
// failed allocation later.
502-
return size;
503-
}
504-
return bits::next_pow2(size);
509+
return sizeclass_to_size(size_to_sizeclass(size));
505510
}
506-
// If realloc(ptr, 0) returns nullptr, some consumers treat this as a
507-
// reallocation failure and abort. To avoid this, we round up the size of
508-
// requested allocations to the smallest size class. This can be changed
509-
// on any platform that's happy to return nullptr from realloc(ptr,0) and
510-
// should eventually become a configuration option.
511+
511512
if (size == 0)
512513
{
514+
// If realloc(ptr, 0) returns nullptr, some consumers treat this as a
515+
// reallocation failure and abort. To avoid this, we round up the size of
516+
// requested allocations to the smallest size class. This can be changed
517+
// on any platform that's happy to return nullptr from realloc(ptr,0) and
518+
// should eventually become a configuration option.
513519
return sizeclass_to_size(size_to_sizeclass(1));
514520
}
515-
return sizeclass_to_size(size_to_sizeclass(size));
521+
522+
if (size > bits::one_at_bit(bits::BITS - 1))
523+
{
524+
// This size is too large, no rounding should occur as will result in a
525+
// failed allocation later.
526+
return size;
527+
}
528+
return bits::next_pow2(size);
516529
}
517530

518531
/// Returns the alignment that this size naturally has, that is
@@ -549,9 +562,7 @@ namespace snmalloc
549562
// sizeclass calculation. We use the same fast path constant to
550563
// move the case where result==0 to the slow path, and then check for which
551564
// case we are in.
552-
constexpr size_t SmallSizeClassUpperBound =
553-
sizeclass_to_size(NUM_SMALL_SIZECLASSES - 1) - 1;
554-
if (SNMALLOC_LIKELY((result - 1) < SmallSizeClassUpperBound))
565+
if (is_small_sizeclass(result))
555566
return result;
556567

557568
// We are in the slow path, so we need to check for overflow.

src/test/func/malloc/malloc.cc

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,9 @@ int main(int argc, char** argv)
265265
test_calloc(0, size, SUCCESS, false);
266266
}
267267

268+
// Check realloc(nullptr,0) behaves like malloc(1)
269+
test_realloc(nullptr, 0, SUCCESS, false);
270+
268271
for (smallsizeclass_t sc = 0; sc < NUM_SMALL_SIZECLASSES; sc++)
269272
{
270273
const size_t size = sizeclass_to_size(sc);
@@ -277,6 +280,8 @@ int main(int argc, char** argv)
277280
test_realloc(our_malloc(size), size2, SUCCESS, false);
278281
test_realloc(our_malloc(size + 1), size2, SUCCESS, false);
279282
}
283+
// Check realloc(p,0), behaves like free(p), if p != nullptr
284+
test_realloc(our_malloc(size), 0, SUCCESS, true);
280285
}
281286

282287
for (smallsizeclass_t sc = 0; sc < (MAX_SMALL_SIZECLASS_BITS + 4); sc++)

0 commit comments

Comments
 (0)