Search code examples
clanguage-lawyerc99undefined-behaviorc11

Printing null pointers with %p is undefined behavior?


Is it undefined behavior to print null pointers with the %p conversion specifier?

#include <stdio.h>

int main(void) {
    void *p = NULL;

    printf("%p", p);

    return 0;
}

The question applies to the C standard, and not to C implementations.


Solution

  • This is one of those weird corner cases where we're subject to the limitations of the English language and inconsistent structure in the standard. So at best, I can make a compelling counter-argument, as it's impossible to prove it :)1


    The code in the question exhibits well-defined behaviour.

    As [7.1.4] is the basis of the question, let's start there:

    Each of the following statements applies unless explicitly stated otherwise in the detailed descriptions that follow: If an argument to a function has an invalid value (such as a value outside the domain of the function, or a pointer outside the address space of the program, or a null pointer, [... other examples ...]) [...] the behavior is undefined. [... other statements ...]

    This is clumsy language. One interpretation is that the items in the list are UB for all library functions, unless overridden by the individual descriptions. But the list starts with "such as", indicating that it's illustrative, not exhaustive. For example, it does not mention correct null-termination of strings (critical for the behaviour of e.g. strcpy).

    Thus it's clear the intent/scope of 7.1.4 is simply that an "invalid value" leads to UB (unless stated otherwise). We have to look to each function's description to determine what counts as an "invalid value".

    Example 1 - strcpy

    [7.21.2.3] says only this:

    The strcpy function copies the string pointed to by s2 (including the terminating null character) into the array pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined.

    It makes no explicit mention of null pointers, yet it makes no mention of null terminators either. Instead, one infers from "string pointed to by s2" that the only valid values are strings (i.e. pointers to null-terminated character arrays).

    Indeed, this pattern can be seen throughout the individual descriptions. Some other examples:

    • [7.6.4.1 (fenv)] store the current floating-point environment in the object pointed to by envp

    • [7.12.6.4 (frexp)] store the integer in the int object pointed to by exp

    • [7.19.5.1 (fclose)] the stream pointed to by stream

    Example 2 - printf

    [7.19.6.1] says this about %p:

    p - 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.

    Null is a valid pointer value, and this section makes no explicit mention that null is a special case, nor that the pointer has to point at an object. Thus it is defined behaviour.


    1. Unless a standards author comes forward, or unless we can find something similar to a rationale document that clarifies things.