Original answer is left below, because I think it has interesting references to standard.
First a short answer: many others think that it is clearly UB, and even if I think that the intent is clear, I could not find a reference in standard showing that the expression is allowed. So the behaviour is undefined per standard.
But as explained below, dereferencing a pointer to an array is equivalent to casting the pointer to the first element of an array. And the cast is perfectly defined by the standard because what lies at the address of an array is the first element of the array if the pointer points to a true array. And if the pointer is null, it is explicitely allowed to cast a null pointer to a type to a null pointer to another type. So just replace the line
return *p;
because the standard does not explicitely specify what should happen with:
return (int *) p; // no UB here even if p is null!
This can be used for a pointer to an array of any type, including multidimensional array: the dereference can be safely replaced by a cast to the immediately underlying sub-array.
It is an interesting corner case. IMHO the standard is unclear on whether it is of not Undefined Behaviour. Here are some hints that could say that it is, from draft n1256 for C99 or n1570 for C11, 6.5.3.2 Address and indirection operators (all emphasizes are mine):
§4 The unary * operator denotes indirection... If the operand has type ‘‘pointer to type’’, the result has type ‘‘type’’. If an
invalid value has been assigned to the pointer, the behavior of the unary * operator is
undefined.
And a note about that part insists that:
Among the invalid values for dereferencing a pointer by the unary * operator are a null pointer...
But it is not that clear, because an array is a derived type that is a non modifiable lvalue and can only be used in two contextes:
Using *p[i]
would certainly be UB, because we start by doing arithmetics on a null pointer and then dereference the resul. No doubt here
But in the shown code (return *p;
), we are in the first context, meaning that we only convert the array to a pointer. And the same note (on same paragraph) says:
Thus, &*E is equivalent to E (even if E is a null pointer)...
As p
is a pointer to array, it shall be applied the semantics of multidimensional arrays.
And the paragraph 6.5.2.1 Array subscripting of same standard is explicit on what happens for multidimensional arrays:
§ 3 Successive subscript operators designate an element of a multidimensional array object.
If E is an n-dimensional array (n ³ 2) with dimensions i ´ j ´ . . . ´ k, then E (used as
other than an lvalue) is converted to a pointer to an (n - 1)-dimensional array with
dimensions j ´ . . . ´ k. If the unary * operator is applied to this pointer explicitly, or
implicitly as a result of subscripting, the result is the pointed-to (n - 1)-dimensional array
IMHO this clearly states that *p
is (int *) p
so the function f
is required to return a null pointer when it receives a null pointer.
But the first comment cited here let think that any * operator applied to a null pointer leads to UB. The second part of same comment proves that it is false, but comments are not normative. So to avoid to be burned by a future version of an optimizing compiler actively chasing possible UB, I would treat that as UB and never use it in real code, even if I really think that it is allowed.
NOTE: I know that comments are not normative, but they are here to help to understand the standard. So when one comment explicitely says that &*E
is equivalent to E
(even if E
is a null pointer) it really means that provided the result is still used for its address, applying the operator * to a null pointer is not necessarily UB.