4

The man page of realloc() says:

The realloc() function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized.

However, the man page doesn't say anything about what happens if the new size is less than the old size. For example, if I have the following code:

ptr = realloc(ptr, nsize); // Where nsize < the original size and ptr is of type void **

If the original size is size, does that mean ptr + nsize + 1 still contains allocated entries?

Any help is appreciated.

S. Sharma
  • 203
  • 1
  • 11

2 Answers2

8

first you probably mean:

void **ptr = malloc(nsize*2);

then

ptr = realloc(ptr, nsize);

or the safe way:

void **ptr2 = realloc(ptr, nsize);
if (ptr2 != NULL)
{
   ptr = ptr2;
} // else failure

because using realloc(ptr,nsize) to set the value of ptr is undefined behaviour and probably crashes.

Now, the system reduces the memory size as stated in Can I assume that calling realloc with a smaller size will free the remainder?

Now your question is:

If the original size is size, does that mean ptr + nsize + 1 still contains allocated entries?

you have no guarantee of that. This is undefined behaviour from ptr + nsize already (thanks Sourav).

Why? this area doesn't belong to your program anymore.

You could have bugs reading past the new smaller array, which would yield valid results if the old data was there, which is probable, true, but:

  • the system could keep the same memory location, but reuse this block immediately for other data
  • the system could move the new data to another memory location (so old ptr would be different from new ptr, hence the return value that some people ignore and it "works" until it crashes), in that case, what's behind is complete irrelevant data.

If both conditions above don't happen, it's very likely that the data is unchanged. realloc won't going to set to 0 some memory that isn't supposed to be used. Some debug frameworks (which ones I don't remember) put a pattern when deallocating memory so if you stumble on this pattern in your program it's a clear indication that you're reading an unallocated/uninitialized memory, but it has overhead, so it's not done by default. You can "overload" the memory allocation functions to do that yourself too.

Anyhow, make sure you're not reading past the new array, as what you'll find isn't guaranteed.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • `you have no guarantee of that.`..umm, why? `nsize` is the new size, right? – Sourav Ghosh Mar 13 '19 at 13:47
  • the plus one is irrelevant, yes. But if `nsize` is the new size, then all data past it is unreliable. That's what I meant. – Jean-François Fabre Mar 13 '19 at 13:49
  • Thanks for the response (and yes, I didn't mean to write the `void **` in front of `ptr` as that would be initializing it). So, prior to reallocating the memory, should I loop for the data in `ptr + nsize + 1` and free it so that there isn't a memory leak? – S. Sharma Mar 13 '19 at 14:56
  • free it? no `realloc` does that already. You can set it to 0 or 0xDEADBEEF or whatever just in case you stumble on that afterwards. – Jean-François Fabre Mar 13 '19 at 15:22
  • It's a minor point, and safe programming certainly requires using a temporary to store the `realloc` return value, but it's hard to see how `realloc` could fail if it is asked to reduce the size of the allocation, since it is required to preserve the previous allocation if an error is returned. So even if `realloc` wants to copy the storage into a new region and can't find memory to do that, it still has the option of just returning the original allocation. – rici Mar 13 '19 at 16:43
  • that's really a good point. We could imagine that the system would deallocate the memory, then a higher priority task/interrupt/whatever takes over and allocates some memory, stealing the process memory. I don't know if it's a real scenario though. You may also point out that `realloc` rarely/never returns `NULL` in modern systems because of virtualization and paging (I'm not a specialist) – Jean-François Fabre Mar 13 '19 at 16:59
  • But it can't deallocate the memory until it definitely has the replacement memory and has copied the original content, because if it fails it must not modify the original. If the new allocation is somehow stolen, the copy will fail. Anyway, like I said, minor point. Both `realloc` and `malloc` will fail on modern systems if the request is larger than the process VM limit, see `rlimit`. In particular, `malloc((size_t)-1)` almost certainly returns `NULL`. – rici Mar 13 '19 at 17:06
  • okay, realloc with shrink _cannot_ fail. If another block is allocated to move the memory and that fails, then the system will probably keep the current block, not moving the data. You're right. interesting chat. – Jean-François Fabre Mar 13 '19 at 17:13
5

First of all,

 void **ptr = realloc(ptr, nsize); 

is wrong, as you're using ptr uninitialized (that is being defined here), and as per realloc() function description from C11, chapter §7.22.3.5

If ptr is a null pointer, the realloc function behaves like the malloc function for the specified size. Otherwise, if ptr does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to the free or realloc function, the behavior is undefined. [...]

So, your code invokes undefined behavior, as you're passing a pointer which contains an indeterminate value.

However, considering your case to be something like

void **ptr = malloc(size);
assert (ptr);
ptr = realloc(ptr, nsize);

it is a very poor usage, in case realloc fails (where it does not alter the original memory and return NULL), you'll end up losing the actual pointer, too. Use an intermediate variable to store validate the returned pointer, and then assign it back to the original variable, as needed.

That said, re-check the quote (emphasis mine)

The realloc() function changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized.

So, to answer

If the original size is size, does that mean ptr + nsize + 1 still contains allocated entries?

No, we can;t say. After the successful realloc() call, we are only allowed to access up to ptr + nsize - 1. Trying to read/write ptr + nsize and onwards is undefined, as that memory location does not belong to your process anymore and that memory location is "invalid".

You should not have any need to bother about the content beyond ptr + nsize - 1, anyways.

Sourav Ghosh
  • 133,132
  • 16
  • 183
  • 261