Using original pointer after realloc?
Asked Answered
M

2

11

I was reading Richard Reese's new (May 2013) O'Reilly book "Understanding and Using C Pointers", and I have a question about some code therein, on page 87.

if (++length > maximumLength) {
    char *newBuffer = realloc (buffer, maximumLength += sizeIncrement);

    if (newBuffer == NULL) {
        free (buffer);
        return NULL;
    }

    currentPosition = newBuffer + (currentPosition - buffer);
    buffer = newBuffer;
}

I hope the names of the variables are self-explanatory; if context is needed, I will edit to provide the entire chunk of code and not just this excerpt.

My question is about the line currentPosition = newBuffer + (currentPosition - buffer);. My understanding of realloc() is that when the new allocation succeeds, the originally allocated memory is freed. If that is correct, then the line in question is using dangling pointers, innit? Both buffer and currentPosition on the RHS of that expression are pointers to memory that has been freed.

My instinct would be to rewrite this to avoid using the dangling pointers by using length, which after all is already around. I want to replace those last two lines with:

buffer = newBuffer;
currentPosition = buffer + length;

However, presumably the code as written works because the two pointers still hold addresses (albeit of garbage), and the offset between those two addresses can still be calculated as a way of reassigning currentPosition. So am I being merely persnickety in feeling uneasy about this?

To generalize the question: once a pointer is dangling, is it safe to use the address contained in the pointer for any purpose, such as calculating offsets? Thanks.

Moorehead answered 29/7, 2013 at 0:56 Comment(5)
At the time of reallocation, length is one greater than the size of the buffer (maximumLength before the adjustment). You should be using currentPosition = buffer + length - 1 if I'm interpreting the meanings correctly.Coyotillo
I checked that before posting the question, actually. The book's code initializes both length and currentPosition to zero. length is incremented in the first conditional, so it is always one past the index of the last added element. currentPosition is where the new element is to be added, and gets incremented after the add. This not how I would have written the code to begin with, but taking the code as given, buffer + length is correct.Moorehead
So currentPosition is a pre-composed buffer + length? I stand corrected (and slightly bemused by the redundancy).Coyotillo
Ugh. Ugly code. Maybe add an else at least? My critique is to R. Reese, not the OP.Martimartial
@Jim, yes, and sadly, it's quite representative of the rest of the book.Moorehead
A
10

once a pointer is dangling, is it safe to use the address contained in the pointer for any purpose, such as calculating offsets?

No, it is not safe. After free the pointer value is an invalid address and an invalid address cannot be used for pointer arithmetic without invoking undefined behavior.

Anitraaniweta answered 29/7, 2013 at 1:0 Comment(10)
Source? I'd not have expected this.Carpi
@CoryNelson C11, 6.5.6p8 pointer arithmetic. If the result is not in the array object or one past the last element => undefined behavior.Anitraaniweta
@Ouah, I think that refers to when you try to dereference the result.Miscue
@Miscue read the paragraph, pointer arithmetic cannot be done with null pointer value or invalid address.Anitraaniweta
@Anitraaniweta This section applies "When an expression that has integer type is added to or subtracted from a pointer" -- in his example he is subtracting two pointers.Carpi
@CoryNelson same rules apply for pointer subtraction, see 6.5.6p9. You cannot do pointer subtraction with invalid pointer values.Anitraaniweta
It would help to cite 6.2.4p2: "The value of a pointer becomes indeterminate when the object it points to (or just past) reaches the end of its lifetime."Coyotillo
Thanks for the pointer (har har) to the standard, @ouah. I looked up 6.5.6p9 and it explicitly says "When two pointers are subtracted, both shall point to elements of the same array object, or one past the last element of the array object." So since these pointers aren't pointing anywhere, you're right, this is undefined behavior. It may work on most implementations, but it's nonstandard, and therefore (as I suspected) inadvisable.Moorehead
@JimBalter: The statement that there is no implementation where this would fail is inconsistent with the statement that the optimizer might reuse buffer, resulting in the wrong result, since an implementation that does the latter would be an implementation where this fails. Additionally, it is incorrect to reason that there is no implementation where this would fail “since realloc cannot change the value of buffer”. A pointer is not required to be implemented as an address. In theory, it might be a handle to information about an object, and that information may be altered by realloc.Yoruba
@JimBalter: Per this page, the C implementation for the Symbolics Lisp Machine did not use addresses for pointers. Even traditional C implementations may have features to prevent incorrect pointer use. For example, in this one from Intel, the free implementation will “find all pointers that point to the block being freed” and change them. After that, arithmetic on those pointers will return incorrect results.Yoruba
M
0

It is safe to use the dangling pointer (e.g. for "pointer arithmetic") as long as you don't try to dereference the pointer (i.e. apply the operator *).

Miscue answered 29/7, 2013 at 1:1 Comment(1)
This is wrong because the C standard does not guarantee that arithmetic on former pointers to objects that no longer exist works. You might reason that pointer are implemented as memory addresses, and arithmetic on addresses obviously works, but pointers are not necessarily implemented that way. Additionally, the optimizer is permitted to make deductions based on the rules of C, which can lead to surprising behavior.Yoruba

© 2022 - 2024 — McMap. All rights reserved.