16

Any reasons why this can not be standard behavior of free()?

multiple pointers pointing to the same object:

#include <stdlib.h>
#include <stdio.h>

void safefree(void*& p)
{
    free(p); p = NULL;
}

int main()
{
    int *p = (int *)malloc(sizeof(int));
    *p = 1234;
    int*& p2 = p;
    printf("p=%p p2=%p\n", p, p2);
    safefree((void*&)p2);
    printf("p=%p p2=%p\n", p, p2);
    safefree((void*&)p); // safe

    return 0;
}

assignment from malloc demands cast from void*

vice versa:

safefree() demands cast to void*& (reference)

phuclv
  • 37,963
  • 15
  • 156
  • 475
Oleg Razgulyaev
  • 5,757
  • 4
  • 28
  • 28
  • 4
    Any reasons why it *should* be standard? What value does it add? What sense does it make? – AnT stands with Russia Apr 16 '10 at 17:36
  • @AndreyT: it would be great if pointer points to something meaningfull or to null – Oleg Razgulyaev Apr 16 '10 at 18:19
  • 4
    Yes, but in C/C++ it is generally not achievable. Setting a pointer to null in `free` wouldn't make much of a difference. In general case you'll have multiple pointers pointing to the same object and setting just one of them to null won't solve anything. – AnT stands with Russia Apr 16 '10 at 18:26
  • compiler should mark pointers casted from void* (and references) as fixed; freeing "hanging" pointers should generate compilation error – Oleg Razgulyaev Apr 16 '10 at 21:26
  • 1
    I think this is a duplicate of e.g.: http://stackoverflow.com/questions/704466/why-doesnt-delete-set-the-pointer-to-null (April 1, 2009) and of: http://stackoverflow.com/questions/1931126/is-it-good-practice-to-null-a-pointer-after-deleting-it (December 18 2009) – Joseph Quinsey May 05 '10 at 23:28
  • because this won't invalidate the other points to p thus isn't "safe" at all – paulm May 16 '15 at 12:36
  • @OlegRazgulyaev : Assignment from malloc doesn't demand a cast in c. Is the statement specific to C++? – sjsam Apr 27 '16 at 17:06

9 Answers9

29

If it did, you would have to pass a pointer to a pointer to the function:

int * p = malloc( sizeof( int ));
free( & p );

which I'm sure many people would get wrong.

Paul Stephenson
  • 67,682
  • 9
  • 49
  • 51
  • 11
    This, plus of course many times p is only going to be a local stack variable anyway in which case having set it to NULL becomes irrelevant as soon as it goes out of scope. Also, if there are multiple references to the same block of memory then they wouldn't all end up set to NULL if one of them was free'd, so it really wouldn't help much. – Vicky Apr 16 '10 at 09:40
  • 1
    And since there is no generic pointer-to-pointer type, you would need a separate `free` for each type of pointer. – caf Apr 16 '10 at 09:52
  • @caf, actually there is: void **. – CMircea Apr 16 '10 at 10:14
  • In C++ free() could take a reference to a pointer and the syntax is the same, but for C compatibility it probably won't ever do that. – AshleysBrain Apr 16 '10 at 10:31
  • 2
    `void **` is **not** a generic pointer to pointer type. `void **` is a pointer to specific object type: `void *`. A `void **` pointer should only ever be dereferenced if it points to an actual `void *`. – caf Apr 16 '10 at 10:45
  • 1
    @AshleysBrain: The reference method has the same problem as the mooted C pointer-to-pointer implementation - for example, you can't initialise a reference of type `void *&` from an expression of type `int *`. – caf Apr 16 '10 at 13:44
  • @Neil: Perhaps some people would get it wrong, but that's not really a valid reason for why it's not done that way. @Marcelo's answer cuts to the heart of the matter, which is that there is no guarantee that the argument passed to `free` will be an **lvalue**. – Dan Moulding Apr 16 '10 at 17:35
  • 1
    @Dan well obviously if we are changing the way free() works, we can specify that its parameter must be an lvalue. –  Apr 16 '10 at 18:00
  • I've often wished free would return NULL so freeing a pointer and setting it to NULL could be achieved in a single stroke. Not that big a deal that it doesn't work that way, though. – James Morris Apr 17 '10 at 00:08
  • @Neil: we're not changing the way it works, we're explaining why it works the way it does ;) – Dan Moulding Apr 17 '10 at 00:45
  • Only after 10 years of coding C did I realize that void ** could be dereferenced and wasn't just a thing silly people used. In all honesty I think it's 2 reasons. First being that setting it to 0 is wasteful clock cycle for code where every cycle matters and you use practices to avoid using a variable after it is freed; Second being exactly this post: difference between void * and void ** is something that is not covered in most C courses or books very well. – Dmytro Sep 03 '16 at 06:05
27

Simple answer: because you might be freeing an expression, e.g. free(find_named_object("foo")).

In more detail: The free function takes a void* parameter, which is the address of the memory to free. This doesn't confer to the function any knowledge of the original variable that supplied the address (or, for that matter, whether there even exists a variable). Just setting the parameter passed in to NULL would do nothing either, since it's just a local copy of the address.

Marcelo Cantos
  • 181,030
  • 38
  • 327
  • 365
  • 2
    Passing an expression to free should not be a reason, because in C expressions are not first class objects. In your example, the expression returns a pointer, which in turn is passed to free. – Vijay Mathew Apr 16 '10 at 09:45
  • const char *s="foo"; char *p=(char*)s; free(p); - compiles ok – Oleg Razgulyaev Apr 16 '10 at 09:49
  • 2
    @Vijay, I don't understand your point. The expression does indeed return a pointer, which is then passed to free. How, then, can `p` be set to NULL? There is no `p` to set. – Marcelo Cantos Apr 16 '10 at 10:08
  • @oraz, that code doesn't compile. You are missing a `*`. I also don't see what relevance it has to my answer. – Marcelo Cantos Apr 16 '10 at 10:10
  • There is absolutely no need to apologise @oraz. (In fact, perhaps I need to apologise; on rereading, my response is more abrupt than I intended.) – Marcelo Cantos Apr 16 '10 at 10:48
  • 1
    @Marcelo By "expression" you meant the pointer returned by find_named_object()? Then you are right. The statement "you might be freeing an expression" caused the confusion as you will always be freeing a pointer. – Vijay Mathew Apr 16 '10 at 14:13
  • 3
    @Vijay: I think by "expression" @Marcelo meant "rvalue". +1 Best answer, BTW. – Dan Moulding Apr 16 '10 at 17:30
  • Thanks for the clarifying remark @Dan. rvalue is exactly what I meant (and should have said :-). – Marcelo Cantos Apr 17 '10 at 00:41
  • @Marcelo: oraz's code, once you undo the markdown formatting applied, reads `const char *s="foo"; char *p=(char*)s; free(p);`. (That's why you thought it was missing asterisks.) –  Sep 23 '10 at 10:42
  • Thank you, @Roger. I should have noticed the italics. – Marcelo Cantos Sep 23 '10 at 10:50
15

Bjarne Stroustrup discussing whether the delete operator should zero its operand. It's not the free() function, but it's a good discussion anyway. Consider points like:

  • What would be zeroed out if you said free(p + 1)?
  • What if there are two pointers to the memory? Why only set one to null if a dangling reference is still left behind?

He also says it was intended but never happened with operator delete:

C++ explicitly allows an implementation of delete to zero out an lvalue operand, and I had hoped that implementations would do that, but that idea doesn't seem to have become popular with implementers.
AshleysBrain
  • 22,335
  • 15
  • 88
  • 124
7

C function parameters are always passed by value, so in order to modify the pointer passed to free() you would need to pass a pointer to the pointer being deallocated, which can lead bugs caused by forgotten & operators.

Secondly, if that pointer had any aliases, the programmer would still be responsible for nulling them out. I could see problems caused by programmers assuming that all references were set to NULL.

Finally, it's not always necessary to set the pointer to NULL. Imagine a function which allocates some memory, does some work and frees it before returning. I could see how setting the pointer to NULL might not seem optimal.

Ferruccio
  • 98,941
  • 38
  • 226
  • 299
  • An optimising compiler+linker could get rid of the last `=NULL`, though as optimisations go this is fairly micro. – PJTraill May 29 '15 at 09:46
5

Because function parameters are passed by value in C. That is, if p == 0x12345 you pass '0x12345' to free(), not p "itself".

user187291
  • 53,363
  • 19
  • 95
  • 127
2

In C, calling a function can never alter the value of the parameters you pass in, so if free(p) altered the value of p by setting it to NULL then this behaviour would be very non-standard indeed.

Paul Stephenson
  • 67,682
  • 9
  • 49
  • 51
2

With reference to the quote from Stroustrup about delete, Peter Norvig also makes a similar remark. He writes (not about C++!):

"Nothing is destroyed until it is replaced"
 - Auguste Comte (1798-1857) (on the need for revolutionary new
   theories (or on the need to do x.f = null in garbage-collected
   languages with destructors))

In my C code, I find the following macro very useful:

#define free(p) free((void *)(p)),(p)=NULL /* zero p */

This, as written, uses its argument twice. But this isn't a problem, as any usage such as free(p++) or free(find_named_object("foo")) will give a compile-time error (lvalue required). And you can hide the macro by using (free)(p++), or by calling it something else e.g. FREE.

Joseph Quinsey
  • 9,553
  • 10
  • 54
  • 77
  • 1
    If you have to remember to use a macro named `FREE` instead of just calling `free`, then why not just remember to null the pointer (if appropriate) instead ;p – Dan Moulding Apr 16 '10 at 17:37
  • 2
    I like it (I'm a sucker for syntactic sugar). But if someone else compiles one of your code snippets, thereby missing the #define, they may get unexpected behavior. +1 for the quote. –  Apr 16 '10 at 22:03
  • #define FREE(p) do { void** x = &(void*)(p) ; free(*x) ; *x = NULL } while(0) – JeremyP Apr 16 '10 at 22:34
  • `free(p++)` will *also* give a compile error, since the result of `p++` isn't an lvalue and can't be assigned to. – caf Apr 16 '10 at 23:39
  • @JeremyP: You can't take the address of the result of a cast operator, it's not an lvalue. – caf Apr 16 '10 at 23:39
  • @uncle brad: You are right. I actually use Free(), which is my wrapper to a wrapper hiding \_\_FILE\_\_ and \_\_LINE\_\_. – Joseph Quinsey Apr 30 '10 at 05:28
  • @caf: You are right about lvalues. I've modified my answer accordingly. – Joseph Quinsey Apr 30 '10 at 05:30
  • deobfuscated it: http://hastebin.com/ulajefofad.coffee. *(&a) avoids compiler ocd cases when compiler doesn't want to set `MyMagicPointer *a = 0`. – Dmytro Sep 03 '16 at 06:14
0

Casting the reference from int *& to void *& is not guaranteed to work. int * simply does not have to have the same size, representation nor alignment requirements as void *.

The proper C++ solution would be to use templates, as Neil Butterworth suggests in a comment. And neither of these ways work in C, obviously - which is where free() comes from.

caf
  • 233,326
  • 40
  • 323
  • 462
  • I've reread the standard not long ago, and it says that casting `int * &` to `void * &` will work, but not the other way around (alignement restrictions on void * are always less that alignement restrictions on other pointer types like int *). But anyway those solution wouldn't work in C and I believe they are not even desirable in the larger picture. – kriss Jul 13 '10 at 13:05
  • @kriss: `int *` and `void *` technically don't even have to have the same *size*. Casting `int *` to `void *` is OK, of course (`void *` must be able to represent every other pointer value). – caf Jul 13 '10 at 23:12
  • where did you got the size part ? I know of specific compilers extension for different kind of pointers of different size, but it works with qualifiers (far pointers, near pointer). Maybe some restriction in standard for function pointers ? However you could still use a `char *` instead of a `void *`, as it is what standard use for byte pointers. – kriss Jul 15 '10 at 00:46
  • @kriss: Simply that it isn't disallowed (in general) for the sizes to be different. In regards to `char *`, it is explicitly specified that: *A pointer to void shall have the same representation and alignment requirements as a pointer to a character type.* - other than that, and a few other restrictions on pointers that must have the same representation and alignment requirements as each other, the representation (which includes the size) of pointers is left unspecified. – caf Jul 15 '10 at 00:57
0

What's this maniac thing with zero ? There are other numbers !

Why should a free pointer should contain zero more than any other distinguished value ?

Practically, in my C++ code I often use watchdog objects and when freeing a pointer reset it not to zero but to the watchdog. That has the added benefit that the methods of the object can still be called with the existing interface and is much more secure and efficient that resetting pointer to zero (it avoid testing objects for zero, I just call the object).

If a free like function say zfree(void * & p) would set p to zero it would forbid my watchdog style (or at least would'nt help).

And as others pointer out , what would be the point to reset a pointer to zero if it goes out of scope ? Just useless code. And what if there is other pointers that contain the same adress, etc.

kriss
  • 23,497
  • 17
  • 97
  • 116
  • 1
    There's no maniac obsession with zero. A constant expression with the value zero has a specific meaning, it is a null pointer constant. I think you've misunderstood the purpose of NULL (or 0). He's not proposing that the freed pointer contain zero, that is not what assigning 0 (or NULL) does; he's proposing setting its value to a null pointer value. A null pointer value is the only usable value that a pointer can contain if it does not point at an actual object so it is at least a plausibly sensible value to assign to a pointer that is no longer pointing at an object. – CB Bailey Jul 13 '10 at 11:38
  • I disagree. A null pointer is not the only usable value that a pointer can contain if it does not point to an actual object. Any value that can't be allocated could be OK, and the compiler could ensure that such distinguished elements are not real address. But the main point is that the only use of this kind of zero is to know if a pointer is used or not. And there is many better methods (say smart pointer with a usage count, or watchdogs) to achieve that. Setting it to zero is one of the worst possible methods, making it default behavior would be even worse. – kriss Jul 13 '10 at 12:59
  • Perhaps I should have been clearer. In C, the valid values that a pointer can contain are a value obtained by taking the address of an object or one past the end of an array object or a null pointer value. Converting an integer to a pointer or address arithmetic are about the only other ways to give a pointer a value; in the first case you have no general guarantee that you won't generate a trap representation and in the latter case you must keep the result inside the array bounds of an array object otherwise you have undefined behaviour. Would you disagree with this? – CB Bailey Jul 14 '10 at 06:40
  • I do not disagree with this. But nothing stop a compiler to reserve some legal addresses to be used as a distinguished element. You could call that allocate real objects if you want. But it's still easy. Also I worked with hardware where 0 was a perfectly valid address for an object (no trap nor anything special there). But the compiler took care that that part of memory wouldn't be accessible. Using zero for ensuring a pointer is not used is just convention, like using negative values for returning errors of functions like write(). – kriss Jul 14 '10 at 10:05