CBFalconer said:
As far as the statement
(assuming a proper environment for it) is concerned, printf is
supposed to dump out a representation of a pointer value. NULL is
a suitable value for a pointer. (void *)0 is a means of generating
that NULL value. I see no reason for any implementor to
distinguish it, dereference it, or to criticize its ancestry, so I
should not expect any problems from the snippet.
I disagree.
While a pointer may be assigned to the null pointer value, it's usage by
library functions is either explicitly included or excluded. In this case,
I agree that an implementation probably will not dereference it, since the
behavior is implementation defined. However, 'probably' doesn't cut it; the
standard must clearly state the legality.
I believe this is an accidental undefined behavior, and should be clarified.
There are two reasons I believe this to be the case.
My first reason is that the behavior is, at the very least, ambiguous. The
standard does not specify the output generated from the %p conversion
specifier. It simply states that it will be "converted to a sequence of
printing characters, in an implementation-defined manner." (see 7.19.6.1).
It is important to note that the standard does not specify if the value will
be dereferenced, nor if it must conform to a range (for example, a
legitimate address inside the address space, or the null pointer).
The only constraint is that it must be a pointer to void. The pointer to
void (from 6.2.5) "shall have the same representation and alignment
requirements as a pointer to a character type." We may therefore conclude it
has a specific representation and alignment, but nothing more.
Several thread responses have asserted that the lack of a range implies that
any pointer value may be used. I tend to agree, and my testing on several
compilers shows that the tested implementations concluded this as well.
However, a gut feeling and real-world implementations do not equate to the
actual specification.
As the actual standard does not include a range requirement, we may conclude
that any properly aligned value, regardless of it being inside the program's
address space or being a null pointer, can be used as an argument. This
also tends to reinforce (but not explicitly state) that the pointer will not
be dereferenced.
So, at the outset, the behavior is ambiguous and possibly undefined, and
therefore requires elaboration.
Now, I will present how the behavior is actually undefined.
We must be very careful about the definitions. Almost all responders have
used their own beliefs about the definitions, rather than using the actual
definitions provided in the standard.
"Each of the following statements applies unless explicitly stated otherwise
in the detailed descriptions that follow: If the argument to a function has
an invalid value (...) or a type (...) not expected by a function with
variable number of arguments, the behavior is undefined." (see 7.1.4)
The definition of invalid values include values outside the domain of the
function, pointers outside the address space of the program, and a null
pointer. Therefore, it must be explicitly stated that the null pointer or a
pointer outside the address space of the program may be used, or else their
use is an undefined behavior.
While I could use that last point as proof enough for my debate, it has
already been contested by others, so I will use another method.
Continuing to 7.19.6.1, the description of the %p conversion specifier, "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."
The actual standard requires "a pointer to void". It does not say (as many
other posters have suggested) "NULL that may be used as a suitable value for
a pointer", "the address of an object", "a pointer that is turned off", "a
pointer pointing to no particular object", "not a dog and not an airplane",
or any of the other analogs presented.
It must be a pointer to void, nothing else, by it's own definition. This is
not ambiguous.
A pointer to void (from 6.2.5) "shall have the same representation and
alignment requirements as a pointer to a character type." There is no
definition expressly allowing the type pointer to void to be compatible with
the NULL pointer constant, nor for a pointer to void to be compatible with a
null pointer.
Referring back to 7.1.4, the question is simply: "Are the null pointer and
the NULL pointer constant proper pointers to void, including the
representation and alignment requirements, and therefore valid values for
this printf conversion specifier?" If not, then it is clearly stated to be
undefined behavior.
Applying the recursive definitions given in 6.2.5, the pointer to void is:
"an object whose value provides a reference to an entity of the void type,
comprising an empty set of values; it is an incomplete type that cannot be
completed."
Continuing with 6.3.2.3, "A pointer to an object or incomplete type may be
converted to a pointer to a different object or incomplete type. If the
resulting pointer is not correctly aligned for the pointed-to type, the
behavior is undefined."
Nowhere in the standard is the null pointer required to have the same
representation and alignment requirements as a pointer to void type, only
that it is implementation defined. The NULL pointer constant is similarly
defined to expand to an implementation defined null pointer constant. (see
7.17)
Therefore, if both the implementation defined values for NULL and the null
pointer are properly aligned as a pointer to void, the results are defined.
Otherwise, this case results in undefined behavior.
Since we cannot assume that one implementation defined behavior (alignment
requirement) will match a second implementation defined behavior (value of
null pointer), when the behaviors are not required by the standard to be
simultaneously met, we must conclude that the test fails. The behavior is
therefore specified as undefined.
I see three possible corrections:
1. Limit the range to valid pointers: The value of the argument must point
to a valid object.
2. Limit the range to within the program, and include null pointers: The
value must be either a valid pointer within the address space of the
program, or a null pointer.
3. Remove the 'pointer to void' requirement: The value must be a constant
integer expression which is converted to a pointer type pursuant to the
rules of 6.3.2.3. (ie, there are no alignment requirements nor address
space requirements, and the value may be a trap representation.)
The first solution would probably break some code, including the case the OP
suggested. The second solution seems to be the intended meaning as written,
although it also breaks a small body of existing code. The third solution
appears to be how the tested implantations performed the operation, but some
implementations may require changes to meet it.
bryanw / frob