James Kuyper said:
You mean like this?
char *pc = malloc(5*sizeof *pi);
long double _Complex *pd = malloc(5*sizeof *pd);
free(&pc);
free(&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.
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.
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;
free(&temp);
temp = pd;
free(&temp);
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.
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
work.
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.
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?
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.
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.
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