1
int *ptr;
...
realloc(ptr,++count*sizeof(int));
      or
ptr=realloc(ptr,++count*sizeof(int));

I noticed if I use option number one more than once, the value of the first memory address (which ptr points to), becomes undefined (although all other values in the memory block are fine and can be accessed by subscripting ptr).

However, I assumed that all realloc does is either shrink or increase the size of a memory block, and ptr will still point to the same memory block and none of its values will change. So if I use option number one, why does the first address in the memory block end up having an unexpected value, because doesn't ptr still point to the same address?

EDIT: I did remember to allocate memory to ptr, just didn't think it work mentioning.

83457
  • 203
  • 1
  • 11
  • 2
    You cannot guarantee that there will be more consecutive space to expand into without grabbing a new chunk of memory. – chris Aug 21 '14 at 20:12
  • 1
    @chris: And even if there is, reusing the pointer passed to `realloc` is UB unless `realloc` failed. – Deduplicator Aug 21 '14 at 20:25
  • @Deduplicator even if it points to the same memory location? – M.M Aug 21 '14 at 20:25
  • @MattMcNabb: There is no conformant way for you to find out, and the answer is yes. (I too once naively thought otherwise...) – Deduplicator Aug 21 '14 at 20:33

2 Answers2

13

The pointer returned by realloc may or may not point to the same address as the pointer you passed to it.

Typically if you're expanding a memory block, there may not be enough room to expand it in place. In that case realloc allocates a new chunk of memory, copies the contents of the old object to the newly allocated chunk, and returns a pointer to the new chunk. If you don't assign the result to a pointer object, you've just lost it (the old chunk is deallocated, and you don't know where the new one is).

realloc can even return a pointer to a newly allocated block of memory if it shrinks the allocated block. There might be good reasons to allocate smaller chunks from a different region of memory.

You also need to be aware that realloc can fail. If it does, it returns a null pointer -- and the old pointer is still valid (just not pointing to a block of memory of the size you wanted). (In principle, realloc can fail even if the new size is smaller than the old one.) So unless your response to an allocation failure is to abort the program, or at least the part of it that uses the pointer, you should assign the result to a different pointer object:

int *ptr = ...;
int *new_ptr;
new_ptr = realloc(ptr, new_size);
if (new_ptr == NULL) {
    /* ptr still points to the old block of memory; you can try something else */
}
else {
    ptr = new_ptr; /* Now you can forget the old pointer value */
}

If realloc succeeds, the pointer value it returns is valid, and the pointer you passed to it is invalid.

If realloc fails, it returns a null pointer, and the pointer you passed to it is still valid.

Keith Thompson
  • 254,901
  • 44
  • 429
  • 631
  • 2
    Perhaps mention that using any pointer derived from the old pointer is only allowed if `realloc` failed: Even if the old is bitwise identical to the new one, using the old one is UB and can result in *interesting* experiences. Also, `realloc(not 0, 0)` is interesting. – Deduplicator Aug 21 '14 at 20:22
  • True that `realloc()` can fail and return `NULL`. But `realloc()` can work and return `NULL` C11dr §J.3.12, hinted by @Deduplicator with `realloc(not 0, 0)`. Maybe `if (new_ptr == NULL && new_size > 0) {`? – chux - Reinstate Monica Aug 22 '14 at 02:57
0

The actual implementation of realloc is implementation-defined, but typically realloc tries to get more memory in two steps. First, it tries to see whether it can expand the allocated block without having to relocate it in memory. If that succeeds, then realloc doesn't have to move anything in memory and the old pointer is valid.

However, there might not be space to expand the allocated block to the new size. For example, you could imagine that there's another block of allocated memory right after the currently-allocated block, so it would be impossible to expand the boundary forward. In that case, realloc has to allocate a brand-new piece of memory to store the larger block, and the old pointer will no longer be valid. This is why you should write

ptr = realloc(ptr, newSize);

instead of

realloc(ptr, newSize);

Of course, there are other reasons why realloc might move memory around. The memory allocator might have specific size requirements for blocks in different parts of memory, or it might try to optimize performance by compacting memory as it goes. In either case, realloc might move memory around without notice, so you may need to change where ptr points.

Hope this helps!

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • Correction: THe old pointer is invalid even if it's bit-for-bit the same as the new pointer. Do not ever use it for anything at all, even comparisons. – Deduplicator Aug 21 '14 at 20:18
  • @Deduplicator The "invalid even if it's bit-for-bit the same" comment at first sounds like you are saying `if (old_ptr == new_ptr) DoNotUse(old_ptr);` Or do you mean "invalid even if it(the data pointed to by the pointers) is bit-for-bit the same"? – chux - Reinstate Monica Aug 21 '14 at 23:11
  • 2
    @chux: The old pointer may not be used for anything at all, including comparison to the new pointer. My "bit-for-bit" is about the pointer, not what it points to. – Deduplicator Aug 21 '14 at 23:14
  • @Deduplicator Now I'm curious. (certainly `old_ptr = new_ptr;` is OK) What about an `old_ptr` would make `printf("%p", (void*) old_ptr)` a problem? Maybe the conversion to `void*`? I'm _beginning_ to see your point. So unless the pointer was originally a `void*`, `printf("%p", (void*) old_ptr)` involves a conversion. Hmmm. – chux - Reinstate Monica Aug 21 '14 at 23:20
  • @chux: No, conversions are not related. The reason any pointer to the old object has *indeterminate value* after `realloc` (or `free`) is that the compiler is justified to assume, after `realloc` returns with a nonzero value, that no other pointer can be equal to the pointer it returned, and thereby that no other object can alias the object pointed-to. So even if the numeric value of the new pointer is the same as the old one, an optimizing compiler can and will compile the expression `old_ptr == new_ptr` to a constant 0. – R.. GitHub STOP HELPING ICE Aug 22 '14 at 01:57
  • @R.. Thanks for the explanation - saved posting a question. Looks like this selectively applies when `__STDC_ANALYZABLE__` is defined: C11dr §L.3 2 "All undefined behavior shall be limited to bounded undefined behavior, except for the following which are permitted to result in critical undefined behavior: ... The value of a pointer that refers to space deallocated by a call to the free or realloc function is used (7.22.3)." – chux - Reinstate Monica Aug 22 '14 at 02:32
  • @Deduplicator I see the "THe old pointer is invalid" depends on if `__STDC_ANALYZABLE__` is defined. – chux - Reinstate Monica Aug 22 '14 at 02:34
  • @chux: That's Annex L, which is an optional specification for "analyzability" that changes the meaning of undefined behavior for many instances of UB. However, per the text you quoted, using a pointer after it was passed to a successful `realloc` is still considered "critical UB". So I don't think whether `__STDC_ANALYZABLE__` is defined makes any difference here. In any case, I'm not aware of any implementations that provide Annex L. – R.. GitHub STOP HELPING ICE Aug 22 '14 at 03:02