printf("%p\n", (void *)0);

T

Trevor L. Jackson, III

Keith said:
Only implicitly. The wording is:

The argument shall be a pointer to void. The value of the pointer
is converted to a sequence of printing characters, in an
implementation-defined manner.




It is not, in my opinion, a "wild extrapolation" of 7.1.4 to assume
that it could mean that any of "a value outside the domain of the
function, or a pointer outside the address space of the program, or a
null pointer, or a pointer to non-modifiable storage when the
corresponding parameter is not const-qualified" is an invalid value.

Sure it is. "Valid values" of pointer include addresses of objects and
the value NULL. Invalid values include addresses that may once have
been valid but are no longer (such as free()'d heap entries), arbitrary
integer values and other kinds of garbage.

I would worry more about printing a stale pointer value than about a
NULL value. Are there library routines that it is safe to pass a stale
pointer to? Hardly. Are there library routines that it is safe to pass
a NULL pointer to? Of course.

printf() is by very strong implication one of the patter and might even
be the only instance of the former.

/tj3
 
S

Stephen Sprunk

Trevor L. Jackson said:
I would worry more about printing a stale pointer value than about a
NULL value. Are there library routines that it is safe to pass a stale
pointer to? Hardly. Are there library routines that it is safe to pass
a NULL pointer to? Of course.

printf() is by very strong implication one of the patter and might even
be the only instance of the former.

For the former, wouldn't passing an invalid pointer to printf() invoke the
same UB that assigning it to another variable does?

For example, if this invokes UB:

void *a, *b;
a = malloc(1);
free(a);
b = a;

Then shouldn't this as well:

void *a;
a = malloc(1);
free(a);
printf("%p\n", a);

I can't conceive of how a system that legally craters on the former
(AS/400?) could work with the latter.

S
 
K

Keith Thompson

Trevor L. Jackson said:
Keith Thompson wrote: [...]
It is not, in my opinion, a "wild extrapolation" of 7.1.4 to assume
that it could mean that any of "a value outside the domain of the
function, or a pointer outside the address space of the program, or a
null pointer, or a pointer to non-modifiable storage when the
corresponding parameter is not const-qualified" is an invalid value.

Sure it is. "Valid values" of pointer include addresses of objects
and the value NULL. Invalid values include addresses that may once
have been valid but are no longer (such as free()'d heap entries),
arbitrary integer values and other kinds of garbage.

"Valid values" of a pointer type depend on the context. As the
operand of a unary "*" operator, a null pointer is invalid; as an
argument to free() it's valid. As an argument to printf() with a "%p"
format, it's intended to be valid, but I don't see how to determine
that from the normative wording of the standard.
I would worry more about printing a stale pointer value than about a
NULL value. Are there library routines that it is safe to pass a
stale pointer to? Hardly. Are there library routines that it is safe
to pass a NULL pointer to? Of course.

printf() is by very strong implication one of the patter and might
even be the only instance of the former.

Certainly I'd *worry* more about printing a stale pointer than a null
pointer.

Even if printf("%p", ...) were defined to accept a stale pointer,
there would still be no way to pass one to it without invoking
undefined behavior just by evaluating it before the call.
 
K

Kalle Olavi Niemitalo

Keith Thompson said:
Even if printf("%p", ...) were defined to accept a stale pointer,
there would still be no way to pass one to it without invoking
undefined behavior just by evaluating it before the call.

It could be done with vprintf, though.

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

void free_print(int dummy, ...)
{
va_list ap;
va_start(ap, dummy);
free(va_arg(ap, void*));
va_end(ap);
va_start(ap, dummy);
vprintf("%p\n", ap);
va_end(ap);
}

int main(void)
{
free_print(0, malloc(1));
return 0;
}
 
T

Trevor L. Jackson, III

Stephen said:
For the former, wouldn't passing an invalid pointer to printf() invoke the
same UB that assigning it to another variable does?

For example, if this invokes UB:

void *a, *b;
a = malloc(1);
free(a);
b = a;

Then shouldn't this as well:

void *a;
a = malloc(1);
free(a);
printf("%p\n", a);

I can't conceive of how a system that legally craters on the former
(AS/400?) could work with the latter.

It probably could not. But printf()'s handling of %p/NULL could be
implementation defined.

/tj3
 
T

Trevor L. Jackson, III

Keith said:
Trevor L. Jackson said:
Keith Thompson wrote:
[...]
It is not, in my opinion, a "wild extrapolation" of 7.1.4 to assume
that it could mean that any of "a value outside the domain of the
function, or a pointer outside the address space of the program, or a
null pointer, or a pointer to non-modifiable storage when the
corresponding parameter is not const-qualified" is an invalid value.

Sure it is. "Valid values" of pointer include addresses of objects
and the value NULL. Invalid values include addresses that may once
have been valid but are no longer (such as free()'d heap entries),
arbitrary integer values and other kinds of garbage.


"Valid values" of a pointer type depend on the context. As the
operand of a unary "*" operator, a null pointer is invalid; as an
argument to free() it's valid. As an argument to printf() with a "%p"
format, it's intended to be valid, but I don't see how to determine
that from the normative wording of the standard.

I got that impression from the fact that in general the only pointer
handling that is valid for object pointers but not valid for NULL
pointers is dereference. Some library functions explicitly forbid
NULLs, where others accept them. Perhaps the gap could be closed by
making accept/forbid a default with the other required to be explicit.

Certainly I'd *worry* more about printing a stale pointer than a null
pointer.
Absolutely.


Even if printf("%p", ...) were defined to accept a stale pointer,
there would still be no way to pass one to it without invoking
undefined behavior just by evaluating it before the call.

Hmmm. Clearly assigning a stale pointer to another variable would be
unacceptable. But your statement implies that:

(void)stale_ptr;

would also be unacceptable. Is that implication accurate?

/tj3
 
K

Keith Thompson

Trevor L. Jackson said:
Keith Thompson wrote: [...]
Even if printf("%p", ...) were defined to accept a stale pointer,
there would still be no way to pass one to it without invoking
undefined behavior just by evaluating it before the call.

Hmmm. Clearly assigning a stale pointer to another variable would be
unacceptable. But your statement implies that:

(void)stale_ptr;

would also be unacceptable. Is that implication accurate?

I believe so. The value of stale_ptr is indeterminate (C99 6.2.4p2),
meaning that it's either an unspecified value or a trap representation
(3.17.2). If it's a trap representation, reading it by an lvalue
expression that doesn't have character type invokes undefined behavior
(6.2.6.1p5).

Realistically, of course, any decent compiler will generate no code for
(void)stale_ptr;
and the program will not trap, but that's just another possible
consequence of undefined behavior.

Digression follows.

I've been wondering why the standard says that the value of a free()d
pointer becomes indeterminate and not that it becomes a trap
representation; since evaluating it invokes undefined behavior anyway,
calling it a trap representation would be less ambiguous and no
strictly conforming program could tell the difference. But I think
I've just figured out why it's worded this way. There actually are
circumstances in which a free()d pointer can be evaluated without
invoking undefined behavior. It's sometimes possible (but not
reliably) to determine that the value of a stale pointer happens not
to be a trap representation. Here's a program that demonstrates this:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
int main(void)
{
int *ptr1, *ptr2;
ptr1 = malloc(sizeof *ptr1);
assert(ptr1 != NULL);
free(ptr1);
/*
* The value of ptr1 is now indeterminate.
* It could be either a trap representation
* or an unspecified value.
*/

ptr2 = malloc(sizeof *ptr2);
assert(ptr2 != NULL);
/*
* ptr2 has a valid value; it points to an object.
*/
printf("ptr2 = %p\n", (void*)ptr2);

/*
* Is the value of ptr1 a trap representation,
* or is it merely unspecified?
*/
if (memcmp(ptr1, ptr2, sizeof(int*)) == 0) {
/*
* The value of ptr1 is unspecified, and happens
* to be the same as the value of ptr2 (malloc()
* re-used the memory that was just free()d). We
* know ptr2 doesn't have a trap representation,
* so we can infer that ptr1 doesn't have a trap
* representation.
*/
printf("ptr1 = %p\n", (void*)ptr1);
}
else {
/*
* ptr1 may or may not have a trap
* representation; we can't reliably tell.
*/
printf("?\n");
}
return 0;
}

This is not a useful program other than as an experiment, and it's not
strictly conforming because it depends on unspecified behavior, but
it's portable and it can *sometimes* print the value of ptr1 after
it's been free()d without invoking undefined behavior. On one
implementation, the output is:

ptr2 = 0xa050248
ptr1 = 0xa050248

If the standard specified that a free()d pointer acquires a trap
representation, this program would invoke undefined behavior on
evaluating ptr1 -- and since ptr2 has the same value and
representation as ptr1, any malloc/free implementation that re-uses
addresses might impose undefined behavior on programs that use it.
 
T

Trevor L. Jackson, III

Keith said:
Trevor L. Jackson said:
Keith Thompson wrote:

[...]


Digression follows.

[example and some discussion snipped]
If the standard specified that a free()d pointer acquires a trap
representation, this program would invoke undefined behavior on
evaluating ptr1 -- and since ptr2 has the same value and
representation as ptr1, any malloc/free implementation that re-uses
addresses might impose undefined behavior on programs that use it.

This poses serious implications for realloc(). At one time the
following sequence was guaranteed to return true:

char * p = malloc( 1 );
assert( p != NULL && "example purposes only" );
*p = '~';
free( p );
char *q = realloc( p, 1 );
return *q == '~';

I.e., if there were no intervening heap oprtations the most recently
free()'d block was still available *and it's contents unchanged*.

Clearly that is no longer true. But the interesting aspect of this is
that the next to last line requires that p be evaluated as part of
calling realloc(). Thus contrary to what I suggest a couple message
ago, printf is/was not the only library function that can/could handle
stale pointers.

And your example requires that ptr1 be evaluated as part of the memcmp()
with ptr2.

ISTM that trap representations for pointers should be defined as part of
the implementation rather than as part of the language. It also STM
that information about those those kinds of considerations should be
available through the compiler rather than only in the compiler's
documentation.

/tj3
 
K

Keith Thompson

Trevor L. Jackson said:
Keith said:
Trevor L. Jackson said:
Keith Thompson wrote:
[...]
Digression follows.

[example and some discussion snipped]
If the standard specified that a free()d pointer acquires a trap
representation, this program would invoke undefined behavior on
evaluating ptr1 -- and since ptr2 has the same value and
representation as ptr1, any malloc/free implementation that re-uses
addresses might impose undefined behavior on programs that use it.

This poses serious implications for realloc(). At one time the
following sequence was guaranteed to return true:

char * p = malloc( 1 );
assert( p != NULL && "example purposes only" );
*p = '~';
free( p );
char *q = realloc( p, 1 );
return *q == '~';

I.e., if there were no intervening heap oprtations the most recently
free()'d block was still available *and it's contents unchanged*.

When was this guaranteed? I'm certain that it invokes undefined
behavior in C99, and I'm fairly sure that it also does so in C89/C90.
Before that, there was no standard as such. Does K&R1 make this
guarantee? (My copy is currently several thousand miles away.)

[snip]
And your example requires that ptr1 be evaluated as part of the
memcmp() with ptr2.

It doesn't evaluate ptr1 *as a pointer*. Any region of memory can be
examined as an array of unsigned char.
ISTM that trap representations for pointers should be defined as part
of the implementation rather than as part of the language. It also
STM that information about those those kinds of considerations should
be available through the compiler rather than only in the compiler's
documentation.

The standard says that trap representations may (but need not) exist,
and that evaluating something with a trap representation invokes
undefined behavior. As far as I know, that's all it says and all it
needs to say. Implementations are always free to document whatever
they like.
 
W

Wojtek Lerch

This poses serious implications for realloc(). At one time the
following sequence was guaranteed to return true:

Guaranteed by who? Not by a C standard, I presume?
char * p = malloc( 1 );
assert( p != NULL && "example purposes only" );
*p = '~';
free( p );
char *q = realloc( p, 1 );
return *q == '~';

I.e., if there were no intervening heap oprtations the most recently
free()'d block was still available *and it's contents unchanged*.

Clearly that is no longer true.

It may still be true, on some implementations. It may even be
documented and guaranteed by some implementors. But that doesn't change
the fact that it's undefined behaviour as far as the C standard is
concerned.
But the interesting aspect of this is
that the next to last line requires that p be evaluated as part of
calling realloc(). Thus contrary to what I suggest a couple message
ago, printf is/was not the only library function that can/could handle
stale pointers.

I know implementations where it's perfectly safe to pass a stale pointer
to memcmp(). So what? It's still undefined behaviour as far as the C
standard is concerned.
And your example requires that ptr1 be evaluated as part of the memcmp()
with ptr2.

I think he meant "memcmp( &ptr1, &ptr2, sizeof(int*) )", which evaluates
ptr1 as an lvalue but doesn't use the lvalue to read the stored value.
 
T

Trevor L. Jackson, III

Keith said:
Trevor L. Jackson said:
Keith Thompson wrote:

Keith Thompson wrote:

[...]
Digression follows.

[example and some discussion snipped]

If the standard specified that a free()d pointer acquires a trap
representation, this program would invoke undefined behavior on
evaluating ptr1 -- and since ptr2 has the same value and
representation as ptr1, any malloc/free implementation that re-uses
addresses might impose undefined behavior on programs that use it.

This poses serious implications for realloc(). At one time the
following sequence was guaranteed to return true:

char * p = malloc( 1 );
assert( p != NULL && "example purposes only" );
*p = '~';
free( p );
char *q = realloc( p, 1 );
return *q == '~';

I.e., if there were no intervening heap oprtations the most recently
free()'d block was still available *and it's contents unchanged*.


When was this guaranteed? I'm certain that it invokes undefined
behavior in C99, and I'm fairly sure that it also does so in C89/C90.
Before that, there was no standard as such. Does K&R1 make this
guarantee? (My copy is currently several thousand miles away.)

I'm not sure about K&R, but the unix implementations did. The only
reason I know about this is that I had to emulate it when replacing
Mcrosoft's defective heap manager with an ANSI- and legacy-compatible
library in the mid 80's.

/tj3
 
T

Trevor L. Jackson, III

Wojtek said:
Guaranteed by who? Not by a C standard, I presume?



It may still be true, on some implementations. It may even be
documented and guaranteed by some implementors. But that doesn't change
the fact that it's undefined behaviour as far as the C standard is
concerned.

So realloc() is explicitly defined to not handle stale pointers? Or are
you inferring that conclusion from general language re stale pointers
and traps?
I know implementations where it's perfectly safe to pass a stale pointer
to memcmp(). So what?

What so is that library function capable of or intended to handle stale
pointers are not unknown.
It's still undefined behaviour as far as the C
standard is concerned.



I think he meant "memcmp( &ptr1, &ptr2, sizeof(int*) )", which evaluates
ptr1 as an lvalue but doesn't use the lvalue to read the stored value.

Probably.

/tj3
 
W

Wojtek Lerch

Trevor L. Jackson said:
So realloc() is explicitly defined to not handle stale pointers? Or are
you inferring that conclusion from general language re stale pointers and
traps?

The latter. What you call a stale pointer is an indeterminate value. Since
the representation of pointers is unspecified, there's no guarantee that the
indeterminate value is not a trap representation. There's no guarantee that
evaluating the first argument to realloc() doesn't produce undefined
behaviour. In other words, there are no guarantees about what the behaviour
is -- i.e. the behaviour is undefined.
What so is that library function capable of or intended to handle stale
pointers are not unknown.

On some implementations. Especially on ones where that's the easiest way to
deal with stale pointers. But what's your point?
 
K

Keith Thompson

Wojtek Lerch said:
Trevor L. Jackson, III wrote: [...]
And your example requires that ptr1 be evaluated as part of the
memcmp() with ptr2.

I think he meant "memcmp( &ptr1, &ptr2, sizeof(int*) )", which
evaluates ptr1 as an lvalue but doesn't use the lvalue to read the
stored value.

Yes, you're right; thanks for the correction.
 
K

Keith Thompson

Trevor L. Jackson said:
So realloc() is explicitly defined to not handle stale pointers? Or
are you inferring that conclusion from general language re stale
pointers and traps?

Evaluating a "stale pointer" invokes undefined behavior (at least if
it's a trap representation, which it may be). Once that happens, it
doesn't matter whether you attempt to pass the value to realloc() or
any other function.
 
W

Wojtek Lerch

Wojtek Lerch said:
The latter.

Actually, both. C99 7.20.3.4p3:

"If ptr is a null pointer, the realloc function behaves like the malloc
function for the specified size. Otherwise, if ptr does not match a pointer
earlier returned by the calloc, malloc, or realloc function, *or if the
space has been deallocated by a call to the free or realloc function*, the
behavior is undefined."
 
K

Keith Thompson

Wojtek Lerch said:
Actually, both. C99 7.20.3.4p3:

"If ptr is a null pointer, the realloc function behaves like the malloc
function for the specified size. Otherwise, if ptr does not match a pointer
earlier returned by the calloc, malloc, or realloc function, *or if the
space has been deallocated by a call to the free or realloc function*, the
behavior is undefined."

The starred text isn't strictly necessary, but since it mentions
pointers earlier returned by the *alloc functions, it was a good idea
to mention it explicitly. It's nice to be able to deduce things from
a single clear statement like this rather than having to go through
half a dozen indirect clauses.

Applying this to the original topic of this thread is left as an
exercise.
 
T

Trevor L. Jackson, III

Wojtek said:
The latter. What you call a stale pointer is an indeterminate value.

No. As I understand it the language of the standard describes those
values as either indeterminate OR trap values. Thus I used a term that
could apply to both kinds of invalid values.

/tj3
 

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
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top