Is this valid code or UB?

Discussion in 'C++' started by Paul, May 23, 2011.

  1. Paul

    Paul Guest

    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?
    Paul, May 23, 2011
    #1
    1. Advertising

  2. On May 23, 4:57 pm, "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?
    > 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
    Anand Hariharan, May 24, 2011
    #2
    1. Advertising

  3. Paul

    Ian Collins Guest

    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
    Ian Collins, May 24, 2011
    #3
  4. On May 23, 2:57 pm, "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?
    > 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>::eek: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.
    Joshua Maurice, May 24, 2011
    #4
  5. * Joshua Maurice, on 24.05.2011 01:35:
    > On May 23, 2:57 pm, "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?
    >> 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>
    Alf P. Steinbach /Usenet, May 24, 2011
    #5
  6. On May 23, 5:38 pm, "Alf P. Steinbach /Usenet" <alf.p.steinbach
    > wrote:
    > * Joshua Maurice, on 24.05.2011 01:35:
    >
    >
    >
    >
    >
    >
    >
    >
    >
    > > On May 23, 2:57 pm, "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?
    > >> 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.
    Joshua Maurice, May 24, 2011
    #6
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Replies:
    2
    Views:
    441
    Adam P. Jenkins
    Jan 18, 2005
  2. Eric Biller

    Embedding VRML-Objects in valid code

    Eric Biller, Nov 13, 2003, in forum: HTML
    Replies:
    6
    Views:
    3,749
    Toby A Inkster
    Nov 14, 2003
  3. Gianni Mariani

    is this code valid ?

    Gianni Mariani, May 17, 2004, in forum: C++
    Replies:
    5
    Views:
    361
    Gianni Mariani
    May 19, 2004
  4. Replies:
    64
    Views:
    1,247
    Dave Thompson
    Dec 20, 2004
  5. Any C code are valid C++ code?

    , Dec 10, 2004, in forum: C Programming
    Replies:
    67
    Views:
    1,165
    Dave Thompson
    Dec 20, 2004
Loading...

Share This Page