Consider `double const*const nix = nullptr; double const*const nax = nix;`. Both objects hold null pointer values, but neither object is a null pointer constant. Here, *constant* means a compile-time expression, not an object qualified with `const`. section 11.1.5
double const*const nix = nullptr; double const*const nax = nix;
The two declarations separate a null pointer value from the expression used to initialize it. section 11.1.5
Before C23, a null pointer constant could be an integer constant expression with value zero, such as `0` or `0L`, or a qualifying `void*` expression such as `(void*)0`. An ordinary `void*` pointer object holding a null value does not thereby become a null pointer constant; nor does casting an arbitrary integer expression to `void*` make one. section 11.1.5 section 18.2
`NULL` must expand to a null pointer constant, but its type is not prescribed: implementations may use an integer zero or `(void*)0`. TAKEAWAY: Prefer `nullptr` to `NULL`; the latter’s expansion can matter in contexts such as variadic function calls. section 11.1.5
Testing the distinction
For an expression `X` that is either a null pointer constant or a `void*` pointer, consider `true ? (struct toto*)0 : (X)`. Its type is `struct toto*` when `X` is a null pointer constant, but `void*` when `X` is a `void*` pointer that is not one. This difference can be used in a compile-time test without evaluating `X`. section 18.2
true ? (struct toto*)0 : (X)
The conditional expression provides the type distinction used by the compile-time test. section 18.2

