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

B

Ben Bacarisse

Keith Thompson said:
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)

I think you meant (free(p), (p) = NULL) (i.e. a comma rather than a
semicolon).

<snip>
 
J

Joe Pfeiffer

James Harris said:
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);
free(&ptr);

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?

That really wouldn't help much: pointer aliasing is extremely common,
and automatically resetting one pointer to a block would still leave a
dangling pointer behind. And, of course, there would be a tendency for
novice programmers to expect all the pointers to a block to have been
reset, which wouldn't be the case.
 
K

Keith Thompson

Shao Miller said:
On 8/5/2013 14:39, Keith Thompson wrote: [...]
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?)

DR #260?:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm

Yeah, that's the one.

The committee response says, in effect, that the representation of a
pointer p can change after free(p):

Values may have any bit-pattern that validly represents them
and the implementation is free to move between alternate
representations (for example, it may normalize pointers,
floating-point representations etc.). In the case of an
indeterminate value all bit-patterns are valid representations
and the actual bit-pattern may change without direct action of
the program.

The DR mentions the issue that the bytes making up the
representation are themselves objects, and that the requirement
that "An object [...] retains its last-stored value throughout its
lifetime." implies that those bytes cannot change. The response
seems to ignore that argument.

On the other hand, I actually like the conclusion. As the DR
discusses, it permits certain optimizations

I think I would have preferred to have an explicit normative
statement added to the standard, saying that objects with
indeterminate values can have their representations change behind
the scenes.

Note that the response applies to more than just objects with
indeterminate values. If a type has multiple representations for the
same value, an object's representation can switch between those
representations -- which changes the values of the unsigned char objects
that make up its representation. This has implications for the use of
memcmp() to compare things other than byte arrays.
 
K

Keith Thompson

Ben Bacarisse said:
I think you meant (free(p), (p) = NULL) (i.e. a comma rather than a
semicolon).

<snip>

Yes. I thought "comma" as I was typing it, but apparently my fingers
were thinking "semicolon". I'll have to talk to them about that.
 
S

Stephen Sprunk

If the compiler warned about that wouldn't it also warn about

printf("x");

Having to declare

(void) printf("x");

would be unusual.

In fact, you will occasionally run across code like that, with the
(void)s added by someone who has their compiler (or lint) warnings
turned up a little too high.

S
 
S

Siri Cruise

James Harris said:
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);
free(&ptr);

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

You can pass lvalues into defines. Adding a few gnucisms:

#define malloc(var,n) ({ \
int n1 = n; var = (malloc)(n1); \
bool success = var || n1==0; \
success; \
})

#define realloc(var,n) ({ \
int n1 = n; typeof(var) var1 = (realloc)(var,n1); \
bool success = var1 || n1==0; \
if (success) var = var1; \
success; \
})

#define free(var) ( \
(free)(var), (var = 0) \
)
 
J

James Kuyper

Actually, it doesn't likely cost anything. If the compiler is even slightly
aggressive about optimization, it will realize that lp is a dead variable
here (no further reads), and hence will optimize the assignment out.

True. The program isn't going to waste any time executing that line of
code, because the compiler is going to spend a completely negligible
amount of time figuring that it can be dropped. On the other hand, the
author wasted his time writing that line of code, and others will waste
time reading it and puzzling over why it's there. I think the waste of
their time is far more important than the time the compiler will waste.
 
S

Shao Miller

Actually, it doesn't likely cost anything. If the compiler is even slightly
aggressive about optimization, it will realize that lp is a dead variable
here (no further reads), and hence will optimize the assignment out.

Right. I forgot 'volatile' in there.
 
J

James Harris

James Kuyper said:
True. The program isn't going to waste any time executing that line of
code, because the compiler is going to spend a completely negligible
amount of time figuring that it can be dropped. On the other hand, the
author wasted his time writing that line of code, and others will waste
time reading it and puzzling over why it's there. I think the waste of
their time is far more important than the time the compiler will waste.

In a tiny program that's true but consider that in a large program where the
pointer is reused as an rval as

*p

a bug might not get noticed because that pointer still refers to valid
memory. The CPU doesn't know that the memory pointed at is unused or has
been reused so it will carry out the access. Setting the pointer to NULL
allows the CPU to check that if the pointer is incorrectly used again its
use will be flagged up. The CPU will do this at absolutely no cost to the
programmer.

It is even worse if the pointer was used on the LHS of an assignment as in

*p = ...

Then the program has just overwritten some memory that could belong to
something else. Or it could have overwritten unimportant memory. Or it could
have overwritten part of a pointer chain.... The key aspect of this is that
it could have introduced one of the worst kind of bugs to track down: one
that 1) is intermittent, 2) comes and goes depending on whether the debugger
is running, or 3) only appears once the code is in production. The effect of
that *p assignment will be totally dependent on the memory layout.

All in all, setting the freed pointer to NULL is a small price to pay for
the security it offers on CPUs which can check such accesses in hardware. Of
course, if there are any other pointers to within the space they too should
be set to NULL by the programmer at the right point.

James
 
J

James Kuyper

In a tiny program that's true but consider that in a large program where the
pointer is reused as an rval as

*p

I was talking very specifically about the case given in Shao Miller's
example above, where the variable's lifetime ends immediately after it
is set to NULL. I strongly recommend nulling any pointer variable that
points into a block of allocated memory immediately after free(), if it
will continue to be used after being nulled. On the other hand, I
strongly recommend avoiding the creation of such variables. Whenever
possible (which, in my experience, is fairly frequently), I arrange to
make sure that pointer objects containing pointers into a piece of
allocated memory have lifetimes that end immediately after the call to
free().
 
T

Tim Rentsch

Keith Thompson said:
Shao Miller said:
On 8/5/2013 14:39, Keith Thompson wrote: [...]
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?)

DR #260?:

http://www.open-std.org/jtc1/sc22/wg14/www/docs/dr_260.htm

Yeah, that's the one.

The committee response says, in effect, that the representation of a
pointer p can change after free(p):

Values may have any bit-pattern that validly represents them
and the implementation is free to move between alternate
representations (for example, it may normalize pointers,
floating-point representations etc.). In the case of an
indeterminate value all bit-patterns are valid representations
and the actual bit-pattern may change without direct action of
the program.

The DR mentions the issue that the bytes making up the
representation are themselves objects, and that the requirement
that "An object [...] retains its last-stored value throughout
its lifetime." implies that those bytes cannot change. The
response seems to ignore that argument.

On the other hand, I actually like the conclusion.

It's a horrendous ruling. I have great respect for people
on the committee, but the decision in this case shows
very muddy thinking.
As the DR discusses, it permits certain optimizations.

That's a bogus argument, because it's comparing apples and
oranges. A code transformation is rightly called an optimization
only when it preserves program semantics; the examples given
work only by taking advantage of more relaxed semantics, and
hence should not be called optimizations. Furthermore there is
no reason to think the gain of such "optimizations" would
amount to much - they don't happen very often, and don't
provide much relative value when they do. The potential
benefits are way not worth the cost in consistency.
I think I would have preferred to have an explicit normative
statement added to the standard, saying that objects with
indeterminate values can have their representations change behind
the scenes.

Note that the response applies to more than just objects with
indeterminate values. If a type has multiple representations for
the same value, an object's representation can switch between
those representations -- which changes the values of the unsigned
char objects that make up its representation. This has
implications for the use of memcmp() to compare things other than
byte arrays.

More muddy thinking. The rule in such cases should be the same
as for regular representations - the program must behave
according to the semantics of the abstract machine, with
optimizations allowed only under the as-if rule. The idea that
object representations (when stored in an object accessible to
the program) may sensibly spontaneously change is lunacy.
 

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. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,076
Messages
2,570,565
Members
47,200
Latest member
Vanessa98N

Latest Threads

Top