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.

verbose
  • 7,827
  • 1
  • 25
  • 40
  • 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. – Casey Jul 29 '13 at 01:33
  • 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. – verbose Jul 29 '13 at 01:51
  • So `currentPosition` is a pre-composed `buffer + length`? I stand corrected (and slightly bemused by the redundancy). – Casey Jul 29 '13 at 01:54
  • Ugh. Ugly code. Maybe add an else at least? My critique is to R. Reese, not the OP. – Jiminion Jul 29 '13 at 01:55
  • @Jim, yes, and sadly, it's quite representative of the rest of the book. – verbose Jul 29 '13 at 02:03

2 Answers2

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.

ouah
  • 142,963
  • 15
  • 272
  • 331
  • Source? I'd not have expected this. – Cory Nelson Jul 29 '13 at 01:01
  • 2
    @CoryNelson C11, 6.5.6p8 pointer arithmetic. If the result is not in the array object or one past the last element => undefined behavior. – ouah Jul 29 '13 at 01:04
  • 1
    @Ouah, I think that refers to when you try to dereference the result. – ROTOGG Jul 29 '13 at 01:06
  • @ROTOGG read the paragraph, pointer arithmetic cannot be done with null pointer value or invalid address. – ouah Jul 29 '13 at 01:08
  • 3
    @ouah 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. – Cory Nelson Jul 29 '13 at 01:12
  • 1
    @CoryNelson same rules apply for pointer subtraction, see 6.5.6p9. You cannot do pointer subtraction with invalid pointer values. – ouah Jul 29 '13 at 01:17
  • 6
    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." – Casey Jul 29 '13 at 01:29
  • 4
    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. – verbose Jul 29 '13 at 01:37
  • 1
    @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`. – Eric Postpischil Jul 29 '13 at 10:48
  • @JimBalter: Per [this page](http://c-faq.com/null/machexamp.html), 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](http://software.intel.com/sites/products/documentation/doclib/iss/2013/compiler/cpp-lin/GUID-9937FAF4-5E87-4D24-BECC-A4B3A0C04511.htm), 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. – Eric Postpischil Jul 30 '13 at 01:07
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 *).

Casey
  • 41,449
  • 7
  • 95
  • 125
ROTOGG
  • 1,106
  • 1
  • 7
  • 17
  • 4
    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. – Eric Postpischil Jul 29 '13 at 10:51