Yes I'm still arguing about arrays. Anyway consider the following:
int main(){
int (*p)[3]=0;
std::cout<<*p<<std::endl;
std::cout<< typeid(*p).name()<<std::endl;
std::cout<< sizeof(*p);
}
Does a valid array type object exist?
Is it UB because it dereferences a null pointer?
Add a missing #include <iostream> for starters.
C++03, 5.2.8 Type identification / 3. typeid will not access the
object, just the static type of the expression, so no undefined
behavior there.
C++03, 4.1 Lvalue-to-rvalue conversion / 2. sizeof will not access the
object, just the static type of the expression, so no undefined
behavior there.
The dereference of the null pointer is harder. Try as I might, I can't
get a sufficiently clear answer from the standard, and some googling
reveals potentially an oversight in specification. Let's get the facts
straight, for starters.
The name p refers to an auto (stack) variable. The text p when
used is an lvalue expression of type "int (*)[3]". The lvalue refers
to an object.
C++03, 5.3.1 Unary operators / 1. The text "*p" is an lvalue
expression of type "int[3]". The lvalue does not refer to an object.
C++03, 8.5 Initializers / 12. The initialization that occurs in
argument passing is equivalent to "T x = a;".
C++03, 8.5 Initializers / 14. The initialization of a non-class
type object with an expression of non-class type will not use user-
defined conversions. It will use only the standard conversions (clause
4).
C++03, 4.1 Lvalue-to-rvalue conversion / 1. If the lvalue does not
refer to an object of the right type, then a lvalue-to-rvalue
conversion on that lvalue is undefined.
Now, unless I'm off my mark, the compiler will do function name
resolution and find the best match function as:
basic_ostream<charT,traits>&
basic_ostream<charT,traits>:
perator<<(const void* p);
To get there, it needs to do a standard conversion sequence (C+
+03, 4 Standard conversions / 1). It starts with the lvalue expression
*p of type "int [3]". First it does an array-to-pointer conversion on
the lvalue of type "int[3]" to get an rvalue of type "int*" (C++03,
4.2 Array-to-pointer conversion / 1). Then it does a pointer
conversion from rvalue "int*" to rvalue "void*" (C++03, 4.10 pointer
conversions / 2).
We had to have hit undefined behavior by now, but the standard
doesn't do a good job of explaining where.
Offhand, I can find two "notes" which say that we hit undefined
behavior as soon as we wrote the expression "*p" where p is null - C+
+03, 1.9 Program execution / 4 and C++03, 8.3.2 References / 4.
Optionally, maybe we could invoke C++03, 3.10 Lvalues and rvalues / 15
(the strict aliasing rule) to get undefined behavior.
Perhaps there's something clearer in the C standard. Here's something
I found offhand through a quick scan: C draft n1256, 6.3.2.1 Lvalues,
arrays, and function designators / 1. If an lvalue does not designate
an object when it is evaluated, then undefined behavior. That's a lot
clearer.
So, your program does have UB. Without doing any more rules lawyering
or reading into the intent of the standard(s), this is how I would
describe it. You have a pointer object. You dereference that pointer
object. You call a function, passing as an argument an expression of
dereference of that pointer object. This necessitates a read of the
pointed-to object. The pointer is null. The pointed-to object does not
exist. Attempts to read (or write) to memory obtained by dereferencing
a null pointer are undefined behavior.
Note that the standard seems to want to state something stronger
judging from the two notes C++03, 1.9 Program execution / 4 and C++03,
8.3.2 References / 4. Note also that the C standard does say something
stronger in n1256, 6.3.2.1 Lvalues, arrays, and function designators /
1. Still, by any of the readings, your program has undefined
behavior.
PS: As a side note, I would probably fix the standard as follows. Note
that
int* x = 0;
int y = *x;
is UB. Actually... well, shit. I thought this would clearly be UB.
However, it appears that C++03 5.17 Assignment operators is missing
the required text: "the right hand side must be an rvalue, and the
standard conversions lvalue-to-rvalue, array-to-pointer, and function-
to-pointer are applied as needed". Let's just pretend that that's
there. With that, then the above example is clearly undefined behavior
because of C++03 4.1 Lvalue-to-rvalue conversions / 1. In this case,
*x is an lvalue expression of type int. In the assignment, that lvalue
expression is converted ala the lvalue-to-rvalue conversion, which
invokes undefined behavior when the lvalue expression does not refer
to an object of the appropriate type. In this case, x is null, so *x
does not refer to any object, and thus int y = *x; has undefined
behavior.
Further note that a standard conversion sequence starts with zero or
one of lvalue-to-rvalue conversion, array-to-pointer conversion, and
function-to-pointer conversion. The reason why it was so hard to
answer the OP's question is that his code neatly dodged the lvalue-to-
rvalue conversion in the standard conversion sequence and instead used
the array-to-pointer conversion:
int (*p)[3]=0;
void* arg = *p;
I would fix this by adding the same text of C++03, 4.1 Lvalue-to-
rvalue conversions / 1 that makes the example "int *x = 0; int y =
*x;" undefined behavior to C++03, 4.2 Array-to-pointer conversion / 1.
(I don't claim that this fixes all problems, but it does seem to
resolve some problems.) Note that this doesn't accomplish the stronger
requirement that *p is always UB, but it does seem to be a minimal
consistent change to the existing standard that accomplishes standard
practice. It perhaps doesn't go far enough.
PPS: Well, that was a lot harder than it should have been. I
encountered no less than two defects in the C++03 standard. Luckily in
this case, these are rather trivial defects which everyone knows what
was intended.