Why not realloc(&ptr, ...) and free(&ptr)?

Discussion in 'C Programming' started by James Harris, Aug 5, 2013.

  1. James Harris

    James Harris Guest

    I cannot get my head round why calls to realloc and free do not have a
    pointer to the initial pointer passed to them as in the following if ptr had
    been returned by malloc.

    retcode = realloc(&ptr, new_size);

    The idea being that if realloc could reallocate the space it would update
    the pointer and return true. If it could not reallocate the space it would
    leave the pointer unchanged and return false.

    In the case of free() the idea is that it would set the pointer to NULL.

    Anyone know why the above two calls were designed the way they were?

    James Harris, Aug 5, 2013
    1. Advertisements

  2. James Harris

    James Kuyper Guest

    You mean like this?

    char *pc = malloc(5*sizeof *pi);
    long double _Complex *pd = malloc(5*sizeof *pd);

    Do you see the problem there? pc and pd have incompatible types; those
    types might have different sizes - there are real-world machines where
    that is the case. Even if they were the same size, they might have
    incompatible representations - though I don't know of any real-world
    example of that.
    Many people think that since void* can accommodate any kind of pointer
    to an object type, void** can accommodate any kind of pointer to a
    pointer to an object type, but C doesn't work that way. It couldn't work
    that way unless it were changed to require all pointers to object types
    to have the same representation and alignment. If free() were changed to
    take a void**, it would have to used this way:
    void * temp = pc;
    temp = pd;
    This not only makes it more complicated to use free(), but it also
    eliminates the supposed advantage of this change: pc and pd are still
    not null.

    Even if C were changed to require all pointers to have the same
    representation, so that void** could work that way, this still would
    violate one of the design principles of C. You shouldn't have to pay
    for the parts of C that you don't use. Well designed code often doesn't
    need to waste time nulling such a pointer, because the pointer will
    never be used again during the time that it would have been null. That's
    true of most of the code I've written that uses free(). Why should code
    that has been written that way pay the cost of having free() waste it's
    time setting the pointer to null?
    If you do write a lot of code that requires nulling the pointer, you can
    always write a macro that wraps free(), and nulls the pointer. That way,
    you get the nulling that you need, while my code still avoids wasting
    time on nulling that I don't need. I'd have no great objection to adding
    such a macro to the C standard library, but it's so trivial to write one
    on your own that I doubt it would ever be approved.

    Also, keep in mind that a nulling version of free() would not
    necessarily do the entire job that needs to be done. In a typical
    program where you would need to null the pointer, there's often multiple
    pointers pointing into various locations inside the block of allocated
    memory. They all need to be nulled, if there's a chance they'll be used
    again before they've been set to a different non-null value.
    James Kuyper, Aug 5, 2013
    1. Advertisements

  3. James Harris

    Shao Miller Guest

    What James Kuyper said. If you'd shared the declaration of 'ptr' in
    your code, it might've been easier to point to. (No pun intended.)

    Also consider:

    void func(void) {
    long * lp;

    lp = malloc(sizeof *lp);
    /* ... */
    lp = NULL;

    There's not really a need to set 'lp' to a null pointer value, here, as
    its lifetime is about to expire. You might want to for security
    reasons, perhaps, but then it does cost time.
    Shao Miller, Aug 5, 2013
  4. Types have been mentioned, but there's another issue with realloc. It
    has to copy the contents of the old memory to the new, but that's wasted
    effort if you don't want that behaviour. For example, if it's a hash
    table you will want the old elements re-hashed for the new size, not
    simply copied into the old offsets. Worse, having them copied makes the
    re-hashing a pain in the neck.

    And when sizing down, you might want to do something to the old extra
    elements (free them, for example) but that will now have to be done
    before the realloc and you'll then have problems if the re-size fails.

    All in all, it's simpler to re-size the memory at let the programmer
    decide what needs to be done with both the old and the new allocation.
    Ben Bacarisse, Aug 5, 2013
  5. James Harris

    James Harris Guest

    So, in summary, pointer to type A and pointer to type B might have different
    sizes and/or different representations of null?

    Trying to understand the ramifications of this.....

    AIUI malloc returns pointer to void. I suppose that, on machines where
    needed, the compiler inserts conversions from malloc's return (which could
    be null) in to a pointer to the right type. It knows the type of the
    variable being assigned to so can add instructions to convert malloc's
    return to a different representation if needed.

    Similarly, when a program calls free() on a pointer the compiler knows that
    that pointer is of type X and can perform any conversions back to the format
    of a void pointer before calling free().

    And when assigning NULL to a pointer or comparing a pointer with NULL the
    compiler can convert length and NULL-representation as necessary.

    OK. I think I've got that.

    So if pointers on a given architecture were always all the same size and
    always had the same representation of NULL no matter what they were pointing
    at it would have been possible to call free(&p) because the free routine
    could have included

    *p = NULL;

    But since the above cannot be guaranteed free() could not set the pointer to
    null. On those odd machines there would be different NULLs.

    Would it still have been OK for free to return NULL so that code could
    include the following?

    p = free(p);

    I know that it does not return NULL. I am trying to understand the design a
    bit better.

    Can you remember any of the machines where pointers to different types had
    different sizes? If you can I'd like to look into some more about how they
    worked and which would still be in use.
    I don't understand this. If all pointers were the same size and represented
    NULL in the same way why couldn't free(&p) work? It could both get and set
    the pointer that p pointed at.

    If, on the other hand, you mean the above code to be used where pointers
    were not of the same size then the problem is that there's no way - sensible
    or otherwise - to dereference a void * so I cannot see how the code could

    In terms of the design of free(), if pointers had different sizes either
    free would need an extra parameter identifying the pointer type or there
    would need to be different versions of free, one per pointer type - or at
    least one per representation of NULL. AFAICS it's OK for mallo to return
    void * because the caller knows what type conversions to carry out; but with
    free() the callee would be expected to deal with the differences - something
    that it could not do without knowing the pointer type.

    Either way, I can see the point about machines with pointer types which
    differ in size or null or both.
    I'm less sure I agree with this. Clearing a pointer (p = NULL) would be
    insignificant when compared with the cost of just calling the free routine
    even if the free routine were to do the absolute minimum. Further, there are
    many examples in the C library of distributed costs. For example, a call to
    printf returns an int that's often not needed. Would you propose another set
    of printf routines which did not return a value on the basis that it could
    save on costs? In fact the whole suite of printf routines are probably an
    order of magnitude (or more) more expensive than they need to be for most of
    what they are used for, because they cope with the general case.
    Sure. That applies any time a region is realloced as well.

    By the way, I take it your comments about free apply to realloc too. AFAICT
    they do, i.e. realloc couldn't know what to change a pointer to on machines
    where there were different sizes of pointer. Again, it could be done as
    above if all pointers on a given machine were of the same size and same NULL
    (nearly all machines in use today?).

    Pity - it would be especially convenient for realloc where the return code
    could indicat success or failure.

    James Harris, Aug 5, 2013
  6. James Harris

    Shao Miller Guest

    It's not special for 'malloc', it's a specialty of 'void *'.
    Why do you care about assigning the pointer to a null pointer value? Is
    it because you're in a loop, or something? Security?
    It seems to me that it's more common that nobody cares what happens to
    'p' after its value has been passed to 'free'. So then it's more common
    to see:


    If 'free' had a return value, then this code would ignore that return
    value, and people with their compiler-warnings turned up would have to
    do something like:

    (void) free(p);
    Beyond sizes, C cares about types. '&p' will yield a 'type-of-p *'. If
    'p' is a 'long *', then '&p' will yield a 'long **'.
    But why bother clearing the pointer? Do you initialize it? If not,
    then why the asymmetric treatment?

    while (cond) {
    void * vp = malloc(4);
    /* ...Test and do work... */
    vp = NULL;

    Clearing the pointer here is a waste, because it's going to be
    initialized on the next iteration.
    'realloc' does indicate success or failure. It has one failure value
    and many success values.

    Something you might not be aware of: C says that a pointer's value
    becomes indeterminate after that value has been passed to 'free'. So given:

    void * vp1 = malloc(4);
    void * vp2 = vp1;
    void * vp3 = vp2;

    All of these pointers contain indeterminate values after the call to
    'free'. This is because the lifetime of the pointed-to object has
    expired, if I recall correctly.
    Shao Miller, Aug 5, 2013
  7. The usual reason is so that the code can determine that the pointer has
    been freed. I.e., it acts as a sentinel value. Among other things, this
    could be used to implement parameter verification; a routine could check to
    see if a passed-in-value was NULL and issue an error message like:

    "p is NULL. Perhaps it has been free'd..."

    Beg to differ. I think that *de-refencing* a pointer value after it has
    been free'd is, of course, UB, but the pointer itself cannot change,
    because of C's call-by-value semantics.

    I.e., in your example above, the value of vp1 cannot be changed as a result
    of being passed to free().
    Kenny McCormack, Aug 5, 2013
  8. James Harris

    Shao Miller Guest

    I see.
    Nah, C explicitly does state[6.2.4p2] that this is an exception to the
    usual "callee cannot affect the caller's objects", because it's actually
    affecting the set of all pointer values; which are good and which are
    bad, m'kay? Doing printf("%p", vp3) after the 'free' might attempt to
    load the value of 'vp3' into an address register and the CPU notices
    that this does not address any storage, so it's a "mistake."
    Shao Miller, Aug 5, 2013
  9. The value of vp1, vp2 and vp3 does not change after calling free(), but
    those existing values all become indeterminate.

    For instance, merely loading an indeterminate value into an address
    register can fail because that address value no longer refers to any
    valid object. You don't need to dereference it to cause problems.

    Stephen Sprunk, Aug 5, 2013
  10. James Harris

    James Harris Guest

    A loop, no. Security, yes. Since the pointer value is no longer valid
    setting it to null makes that clear to any later code. It could also be
    passed to realloc which would then behave as malloc. Of course, it's
    programmer responsibilty to handle any other pointers to the same area.
    If the compiler warned about that wouldn't it also warn about


    Having to declare

    (void) printf("x");

    would be unusual.

    Incidentally, I was once told casts were a bad idea (TM) as they mask other
    issues. Maybe a compiler should complain about them too!

    James Harris, Aug 5, 2013
  11. James Harris

    James Kuyper Guest

    When nulling free()d pointers is necessary (and it seldom has been, in
    my code), the reason for doing so is generally to allow code elsewhere
    in the program to do:

    // *p exists
    // Do things with *p

    If the other piece of code will always know whether or not *p currently
    exists, without having to check the value of p, then p doesn't need to
    be nulled.

    It should, for the sake of consistency.
    A cast is also often the result of someone trying to shut up a compiler
    warning, who didn't understand that the cast doesn't actually fix the
    problem that triggered the complaint. That's one reason why casts should
    be treated with suspicion. However, it is sometimes the legitimate
    purpose of a cast to disable such complaints.
    James Kuyper, Aug 5, 2013
  12. James Harris

    James Kuyper Guest

    Note: be careful about how you use NULL and null. "null" is an adjective
    which can, in ordinary English usage, be applied to many things, but in
    C it's applied only to pointer and character values. NULL is the name of
    a C macro whose expansion is required to be a null pointer constant.
    There is only one NULL, #defined in several C standard headers. Any
    given pointer type may or may not have multiple representations of null
    pointer values; but if it has more than one, they must all compare
    equal. There is a huge variety of different possible null pointer
    constants, though 0 and (void*)0 are overwhelmingly the most common ones.

    When used in a context where a pointer is required, null pointer
    constants are automatically converted to null pointer values. This
    causes many people (yourself included) to use NULL in contexts where "a
    null pointer value" would have been more appropriate. However, not all
    null pointer values are the immediate result of such a conversion
    applied to NULL; most are not.

    Correct. To be precise, that would require a rewrite of the rules
    governing compatible types, a rewrite that would be feasible because of
    the "same representation" requirement.
    Yes, if free() had been defined to return a null pointer value, that
    would also have worked, and no rearrangement to the rules would be needed.
    I've never used such a machine, but others have mentioned them in this
    forum. I never wrote down the names of those machines, just made note of
    the fact that they exist (yes - present tense). Hopefully some of those
    people can give you some examples.

    As I understand it, the single most common reason for such pointers is
    the decision to implement C on systems where the word size (the size of
    the unit of memory that machine addresses refer to) is too big to be
    used as the size of a C byte. On such machines, for types where
    _Alignof(type) > word_size, pointers to objects of that type consist of
    just a machine address for the start of the object. However, for types
    such as char, where _Alignof(type)<word_size, pointers consist of a
    machine address and a byte offset within the word. Depending upon how
    much addressable memory a system has, having to include the byte offset
    could require an increase in the pointer size.
    In the above section, I'm describing the case where the definition of
    free() were changed to take a void** argument, but the rules about
    compatible types were not. Perhaps I should have been clearer about that.
    In the context I was talking about, free() could have been defined as

    void free(void**p)
    // free the block of memory pointed at by *p

    *p = NULL;

    The only pointer that gets dereferenced has the type void**, which is
    perfectly acceptable, not void*, which would be a constraint violation.
    Those are other, more complicated ways of dealing with the issue. They
    all serve to demonstrate how the way free() was actually defined was a
    better choice.
    I don't claim that it's a big cost, merely one that shouldn't be paid.

    James Kuyper, Aug 5, 2013
  13. James Harris

    Shao Miller Guest

    Yup. People with their warnings turned way up have to worry about that
    one, too.
    Shao Miller, Aug 5, 2013
  14. It would have been ok, but probably not particularly useful. Having a
    function always returning the same constant value seems like a bit of
    wasted effort. And since it would still be perfectly possible to write


    many programmers would still do that; it doesn't *enforce* setting p to

    An alternative might have been to make free() a macro that deallocates
    the memory pointed to by its argument, and then sets its argument to
    NULL. Of course you can already do that:

    #define FREE(p) (free(p); (p) = NULL)

    but if the macro in the standard library, the function version wouldn't
    have to be. But it's way too late to change that.
    Even if all pointers have the same size and representation, they're
    still of distinct types.

    But if the C standard assumed that all pointers "smell alike", it could
    theoretically change its type system so that void** is a generic
    pointer-to-pointer type, in the same way that void* is a generic pointer
    type. Then a free()-like function that takes a void** argument could
    work. But that would add a requirement that the argument to free() must
    be an lvalue, which is not currently the case (it commonly is anyway,
    but such a requirement could break *some* code).

    char *ptr malloc(42);
    assert(ptr != NULL);
    free(ptr - 1);

    Historically, I suspect that free() was first implemented when C
    was more weakly typed than it is now; pointers and integers could
    be freely mixed, and dinosaurs roamed the earth. At the same
    time, there was a strong assumption that the programmer knows what
    he's doing, so there was little perceived need for a deallocation
    function to set your pointers to NULL for you. ANSI added stronger
    type checking, but tried not to break existing code.

    But that's probably over-thinking the matter. There are several
    possible ways to write a deallocation function. The author of the
    library whose interface was eventually incorporated into the standard
    picked a way that worked well enough.

    If you're looking for a language and standard library that's a model of
    coherent design, C may not be the best place to look. It is what it is.

    Keith Thompson, Aug 5, 2013
  15. Once you've called realloc(), you can't access the old allocation.

    An alternative is to malloc() the new allocation, then do whatever you
    like with the old and new chunks of memory, then free() the old
    allocation. But that doesn't give you realloc()'s ability to re-use the
    old space.
    Keith Thompson, Aug 5, 2013
  16. I'm not sure that's necessarily an exception.

    The relevant sentence is:

    The value of a pointer becomes indeterminate when the object it
    points to (or just past) reaches the end of its lifetime.

    but I'd argue that that doesn't imply that it has a different value.
    Rather it keeps the *same* value; what changes is that that value
    is determinate before the call to free(), and indeterminate after.

    And the same would apply to a pointer to an object with automatic
    storage duration.

    I think there's a DR (which I can't find at the moment) that says that
    free(p) can actually change the representation of p -- which implies
    that the unsigned char objects that make up its representation can have
    their values change. If I'm remembering it correctly, I'm not at all
    convinced that that can be derived from the normative wording of the
    standard. (Can someone else find a reference to it?)
    Keith Thompson, Aug 5, 2013
  17. James Harris

    BartC Guest

    Because using pointers to pointers is more complex? (Apart from all the
    type-matching issues mentioned.)

    As it is now, it's fairly straightforward, and the call to realloc() matches
    that to malloc(), with both returning a pointer. You can also pass an
    r-value to these functions, although that has limited use.

    In any case it's possible to wrap free() and realloc() with your own
    versions that behave your way, I think someone mentioned.

    However, zeroing one pointer probably is not so useful, as there could be a
    whole bunch of other pointers into the same block. And if the ptr in &ptr
    has been supplied via a function parameter, the original pointer can't be
    BartC, Aug 5, 2013
  18. James Harris

    Shao Miller Guest

    I didn't mean to suggest that it changed the value's bits, but its meaning.
    DR #260?:

    Shao Miller, Aug 5, 2013
  19. We're obviously into "angels and pins" territory here - and all of us trying
    to remain catechismically correct while arguing about religious dogma.

    But, that said, the following ought to be good enough to demonstrate C's
    call-by-value semantics:

    int x = (int) p;
    assert(x == (int) p);
    Kenny McCormack, Aug 5, 2013
  20. It's worse than that. I don't know what I was thinking. Consider it a
    null remark -- there's nothing useful in it.

    Ben Bacarisse, Aug 5, 2013
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.