Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does `realloc` not re-allocate in-place when possible?

From c99:

The realloc function deallocates the old object pointed to by ptr and returns a pointer to a new object that has the size specified by size. The contents of the new object shall be the same as that of the old object prior to deallocation, up to the lesser of the new and old sizes. Any bytes in the new object beyond the size of the old object have indeterminate values.

[..]

The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object could not be allocated.

I am surprised that the standards do not specify that realloc should "try" to do in-place reallocation. Typically, if the reallocation size is lower than the currently allocated size, I would have expected the standards to ensure that the realloc would return the same pointer.

Is there a logic for the standards not to specify that realloc should be in-place if the size is reduced?

like image 620
Remi.b Avatar asked Dec 17 '22 14:12

Remi.b


1 Answers

The notion of "try" doesn't mean much in the context of a standard - how hard does the implementer have to try before they have tried enough? How would one measure compliance?

Many common implementations will work exactly as you suggest: If you're resizing downward, or even resizing upward and the following memory happens to be free, they might return the original pointer after adjusting the housekeeping but not having to copy any data. Yay!

But I can think of lots of reasons why an allocator would not do this even if it were possible:

  • Some allocators keep different arenas for different sizes, where (making this up) it's a different pool for chunks from 1-128 bytes than there are for 64kbytes and larger. The whole scheme breaks down if the "big" pool has to keep small allocations around. This is especially the case if you're intentionally keeping "big" allocations on page boundaries.

  • Multi-thread aware applications often have to take special care to avoid contention so that memory allocation is not a bottleneck. If you're realloc'ing a chunk that was allocated in a different thread, it might be non-blocking to give you a new chunk (with copy) and defer on freeing the old pointer, but allowing you to keep the same pointer would block this or some other thread.

  • A debugging allocator will intentionally return different pointers to make sure the program doesn't incorrectly hang onto an old pointer by mistake: this breaks things sooner rather than later.

I cannot think of a case where a "please try" statement in the standard would change any decisions of any library designer. If keeping the same pointer makes sense for a given implementation, then of course they're going to use it, but if there's an overriding technical reason not to, then they won't.

I'm also not sure I can think of a case where this nudge would make any difference to the user of a library either. You still have to code it to account for all the cases, even one that "tries", so it's not like it's going to save you any code.

In the end, this is an implementation detail that a standard would never handcuff an implementer about, and the library will be judged on its own merits (performance, codesize, reliability, etc.) and this is just one aspect.

You can always code your own allocator if you really need that behavior for some reason.


EDIT: Another reason why an allocator would want to return a different pointer even if reallocing the same size: reducing memory fragmentation.

If my realloc request comes in at a time when there's a lot of free space on either side, the allocator could realize: I could extend this chunk in place (fast and easy), or I could move it some other place and coalesce what's left behind into a much larger free block.

This has been a nagging problem for a customer-written project: written ages ago in 32-bit Delphi, it runs for days at a time with a lot of memory pressure, and eventually the memory is so fragmented that it's unavailable to service modest requests even though there are many hundreds of megabytes free.

Ref: Are concatenated Delphi strings held in a hidden temporary variable that retains a reference to the string?

There's little I can do about this in Delphi, but in C it's very easy to imagine "aggressive avoiding of memory fragmentation" being a property of an allocator.

like image 77
Steve Friedl Avatar answered Dec 31 '22 01:12

Steve Friedl