Can I assume that calling realloc with a smaller size will free the remainder? [duplicate]
Asked Answered
D

5

41

Let’s consider this very short snippet of code:

#include <stdlib.h>

int main()
{
    char* a = malloc(20000);
    char* b = realloc(a, 5);

    free(b);
    return 0;
}

After reading the man page for realloc, I was not entirely sure that the second line would cause the 19995 extra bytes to be freed. To quote the man page: The realloc() function changes the size of the memory block pointed to by ptr to size bytes., but from that definition, can I be sure the rest will be freed?

I mean, the block pointed by b certainly contains 5 free bytes, so would it be enough for a lazy complying allocator to just not do anything for the realloc line?

Note: The allocator I use seems to free the 19 995 extra bytes, as shown by valgrind when commenting out the free(b) line :

==4457== HEAP SUMMARY:
==4457==     in use at exit: 5 bytes in 1 blocks
==4457==   total heap usage: 2 allocs, 1 frees, 20,005 bytes allocated
Diamagnet answered 5/3, 2012 at 22:36 Comment(4)
The header <malloc.h> is not defined by the Standard: prefer to use <stdlib.h>. Also casting the return value from malloc (or realloc) serves no useful purpose and may hide an error (representation of void* and int being different) the compiler would have caught otherwise.Procession
"The size of the memory block pointed to by the ptr parameter is changed to the size bytes, expanding or reducing the amount of memory available in the block."Symbolism
@Procession ok I did not know that. I’ll change in my snippetDiamagnet
Note that Valgrind does not reflect the performance of your normal allocator because it actually replaces the allocator you usually use with one provided by Valgrind.Rattoon
D
31

Yes, guaranteed by the C Standard if the new object can be allocated.

(C99, 7.20.3.4p2) "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."

Deliquescence answered 5/3, 2012 at 22:55 Comment(2)
The C99 draft never mentions anything against realloc doing nothing when the old size equals the new one. I guess all implementations violate it?Diamagnet
C99 7.20.3.4 §4: The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object)Minna
S
30

Yes—if it succeeds.

Your code snippet shows a well-known, nefarious bug:

char* b = (char*) realloc(a, 5);

If this succeeds, the memory that was previously allocated to a will be freed, and b will point to 5 bytes of memory that may or may not overlap the original block.

However, if the call fails, b will benull, but a will still point to its original memory, which will still be valid. In that case, you need to free(a) in order to release the memory.

It's even worse if you use the common (dangerous) idiom:

a = realloc(a, NEW_SIZE);     // Don't do this!

If the call to realloc fails, a will be null and its original memory will be orphaned, making it irretrievably lost until your program exits.

Skeie answered 5/3, 2012 at 22:51 Comment(3)
You are right. I had seen cppcheck complain about this. I guess this one is a killer for exam tests, but really, does this ever, ever happen?Diamagnet
@qdii: in the embedded world all bets are off; also, the os might enforce some limits, eg on virtual memory space via ulimitMinna
Yes this really happens in the real world. Why do you think so much crappy software crashes when you run out of memory (on a system with no swap) rather than giving you a message that it can't perform the requested operation due to lack of memory?Renin
M
4

This depends on your libc implementation. All of the following is conformant behaviour:

  • doing nothing, ie letting the data remain where it is and returning the old block, possibly re-using the now unused bytes for further allocations (afaik such re-use is not common)
  • copying the data to a new block and releasing the old one back to the OS
  • copying the data to a new block and retaining the old one for further allocations

It's also possible to

  • return a null pointer if a new block can't be allocated

In this case, the old data will also remain where it is, which can lead to memory leaks, eg if the return value of realloc() overwrites the only copy of the pointer to that block.

A sensible libc implementation will use some heuristic to determine which solution is most efficient.

Also keep in mind that this description is at implementation level: Semantically, realloc() always frees the object as long as allocation doesn't fail.

Minna answered 5/3, 2012 at 22:51 Comment(3)
Regarding "afaik such re-use is not common", I have never heard of a real-world implementation that leaves the old allocation in-place and does not free the tail for reuse. This would be pathologically bad, albeit legal, behavior.Renin
Are there any standards that define this behavior, or is it really up to libc implementation?Halfandhalf
@rr-: the C standard as well as POSIX leaves it up to the implementation; I'm unaware of any specification that does define the behaviour, but it might be documented in the libc manualMinna
P
2

It seems unlikely that 19995 bytes were freed. What's more likely is that realloc replaced the 20000-byte block by another 5-byte block, that is, the 20000-byte block was freed and a new 5-byte block was allocated.

Printery answered 5/3, 2012 at 22:54 Comment(0)
F
1

The realloc function has the following contract:

void* result = realloc(ptr, new_size)

  • If result is NULL, then ptr is still valid and unchanged.
  • If result is non-NULL, then ptr is now invalid (as if it had been freed) and must not ever be used again. result is now a pointer to exactly new_size bytes of data.

The precise details of what goes on under the hood are implementation-specific - for example result may be equal to ptr (but additional space beyond new_size must not be touched anymore) and realloc may call free, or may do its own internal free representation. The key thing is that as a developer you no longer have responsibility for ptr if realloc returns non-null, and you do still have responsibility for it if realloc returns NULL.

Fractional answered 5/3, 2012 at 23:12 Comment(1)
In addition, passing new_size = 0 is the same as free (ptr) and returns NULL. That would be a case where result == NULL and ptr is not valid anymore.Skirt

© 2022 - 2024 — McMap. All rights reserved.