Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C++ > Is this valid code or UB?

Reply
Thread Tools

Is this valid code or UB?

 
 
Paul
Guest
Posts: n/a
 
      05-23-2011
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?



 
Reply With Quote
 
 
 
 
Anand Hariharan
Guest
Posts: n/a
 
      05-23-2011
On May 23, 4:57*pm, "Paul" <(E-Mail Removed)> wrote:
> 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?



sizeof is evaluated at compile time. Not sure what the standard says
about typeid, but I would like to think that, in this case, it is
evaluated at compile time as well.

The first cout causes UB and hence the behaviour of the entire program
is not defined by the standard.

- Anand
 
Reply With Quote
 
 
 
 
Ian Collins
Guest
Posts: n/a
 
      05-23-2011
On 05/24/11 09:57 AM, Paul wrote:
> 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?


No.

> Is it UB because it dereferences a null pointer?


Yes.

--
Ian Collins
 
Reply With Quote
 
Joshua Maurice
Guest
Posts: n/a
 
      05-23-2011
On May 23, 2:57*pm, "Paul" <(E-Mail Removed)> wrote:
> 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, ****. 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.
 
Reply With Quote
 
Alf P. Steinbach /Usenet
Guest
Posts: n/a
 
      05-24-2011
* Joshua Maurice, on 24.05.2011 01:35:
> On May 23, 2:57 pm, "Paul"<(E-Mail Removed)> wrote:
>> 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.
>


[snip long descent into standard]

He he.

There is a non-normative *comment* somewhere at the start of the standard that
states pretty off-hand that dereferencing a null pointer is UB. Then there is
normative text regarding typeid that says that it's OK for the typeid argument.
Which makes sense, since the compiler can add checking there.

This is a well-known "almost-defect". I think but I am not sure that it has been
fixed in C++0x.


Cheers & hth.,

- Alf

--
blog at <url: http://alfps.wordpress.com>
 
Reply With Quote
 
Joshua Maurice
Guest
Posts: n/a
 
      05-24-2011
On May 23, 5:38*pm, "Alf P. Steinbach /Usenet" <alf.p.steinbach
(E-Mail Removed)> wrote:
> * Joshua Maurice, on 24.05.2011 01:35:
>
>
>
>
>
>
>
>
>
> > On May 23, 2:57 pm, "Paul"<(E-Mail Removed)> *wrote:
> >> 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.

>
> [snip long descent into standard]
>
> He he.
>
> There is a non-normative *comment* somewhere at the start of the standardthat
> states pretty off-hand that dereferencing a null pointer is UB. Then there is
> normative text regarding typeid that says that it's OK for the typeid argument.
> Which makes sense, since the compiler can add checking there.
>
> This is a well-known "almost-defect". I think but I am not sure that it has been
> fixed in C++0x.


Indeed. Just curious, do you think that the following is intended to,
or ought to, be sufficient for UB?
int* x = 0;
*x;

What about this?
int y;
int* z = &y;
*z;

I would think that the second is allowed, and there are absolutely
zero reads or writes to the object y for the purposes of race
conditions. Thus, I'm tempted to say that "*x" ought to be allowed for
the same reason, that there are no reads or writes to the non-existent
object. Only a lvalue-to-rvalue conversion on "*x" would invoke UB, or
a built-in assignment to "*x". Also, as I've showed above, we would
need to plug the array-to-pointer conversion hole if we go this route.
Also, we'd need an explicit rule about reference initialization or
something if we want to maintain the property that references can
never be null in a well formed program without UB. That's all I got
off the top of my head.

Of course, this is all rather pedantic and academic, as I think most
of us in practice could live with "*x" being UB and "*z" not being UB.
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Deprecated functionality with generics (previously valid code now invalid) mortoray@ecircle-ag.com Java 2 01-18-2005 08:18 PM
Any C code are valid C++ code? jrefactors@hotmail.com C++ 64 12-20-2004 06:29 AM
Any C code are valid C++ code? jrefactors@hotmail.com C Programming 67 12-20-2004 06:29 AM
The promotion code "MSUU4C8E3475" is not a valid promotion code Sam-Hong Kong MCDST 2 03-04-2004 06:47 PM
Embedding VRML-Objects in valid code Eric Biller HTML 6 11-14-2003 05:59 PM



Advertisments