Topic: question about upcasting/downcasting a null pointer


Author: jpv50@hotmail.com ("John Vincent")
Date: Thu, 8 Sep 2005 04:20:44 GMT
Raw View
Hi,

As far as I can see from the C++ Standard, casting a null pointer to another
pointer type should yield another null pointer. I have a small test program
I've compiled with gcc which seems to indicate that gcc does not always
follow this rule. Is this a gcc bug I should report to them or am I
misunderstanding something?

The program is and it's output follow:

#include <cstdio>

class A
{
        public:
        void methodA();
        private:
        int     a;
};


class B
{
        public:
        void methodB();
        private:
        int     a;
};


class AB: public A, public B
{
        public:
        void methodAB();
        private:
        int     a;
};


void
A::methodA()
{
        printf( "methodA(): this is %p\n", this );
};

void
B::methodB()
{
        printf( "methodB(): this is %p\n", this );
};


void
AB::methodAB()
{
        methodA();
        methodB();
}

int
main( int argc, char *argv[] )
{
        class AB *p = 0;
        class A *a = p;
        class B *b = p;

        p->methodAB();
        a->methodA();
        b->methodB();
}

this program outputs:

methodA(): this is (nil)
methodB(): this is 0x4
methodA(): this is (nil)
methodB(): this is (nil)

but I would have expected all the values to be (nil)

Thanks in advance for any help or advice

/John.


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Ron Natalie <ron@spamcop.net>
Date: 8 Sep 2005 21:20:05 GMT
Raw View
John Vincent wrote:
> Hi,
>
> As far as I can see from the C++ Standard, casting a null pointer to
> another pointer type should yield another null pointer.

Pretty much.  However, your test is INVALID as it relies on undefined
behavior.

A better test would be to compare the pointers a and b to the null
pointer constant rather than derefencing the null pointer.

Your program doesn't show anything.   The anomoly you think you've
found isn't even on the cast, its on the referencing of the B subobject
within the AB object.  There's no reason for the compiler to assume
within AB::methodAB() that the this pointer doesn't refer to a valid
AB object which it can convert to B without having to test for nullness.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: caj@cs.york.ac.uk (chris jefferson)
Date: Fri, 9 Sep 2005 03:12:29 GMT
Raw View
John Vincent wrote:
> Hi,
>
> As far as I can see from the C++ Standard, casting a null pointer to
> another pointer type should yield another null pointer. I have a small
> test program I've compiled with gcc which seems to indicate that gcc
> does not always follow this rule. Is this a gcc bug I should report to
> them or am I misunderstanding something?
>
> The program is and it's output follow:
>
.
>        class AB *p = 0;
>        class A *a = p;
>        class B *b = p;
>
>        p->methodAB();

You can't call member functions on any old random pointer, you have to
call it on an actual AB. So as soon as you do this you are into the
world of undefined behaviour.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: dsp@bdal.de (=?windows-1252?Q?Daniel_Kr=FCgler?=)
Date: Fri, 9 Sep 2005 03:11:44 GMT
Raw View
Hello John Vincent,

John Vincent wrote:
> Hi,
>=20
> As far as I can see from the C++ Standard, casting a null pointer to=20
> another pointer type should yield another null pointer. I have a small=20
> test program I've compiled with gcc which seems to indicate that gcc=20
> does not always follow this rule. Is this a gcc bug I should report to=20
> them or am I misunderstanding something?

The observed behaviour is not related to a compiler bug, its related to
a C++ code which should never exist, because it causes undefined=20
behaviour (UB), see below.


> The program is and it's output follow:
>=20
> #include <cstdio>
>=20
> class A
> {
>        public:
>        void methodA();
>        private:
>        int     a;
> };
>=20
>=20
> class B
> {
>        public:
>        void methodB();
>        private:
>        int     a;
> };
>=20
>=20
> class AB: public A, public B
> {
>        public:
>        void methodAB();
>        private:
>        int     a;
> };
>=20
>=20
> void
> A::methodA()
> {
>        printf( "methodA(): this is %p\n", this );
> };
>=20
> void
> B::methodB()
> {
>        printf( "methodB(): this is %p\n", this );
> };
>=20
>=20
> void
> AB::methodAB()
> {
>        methodA();
>        methodB();
> }
>=20
> int
> main( int argc, char *argv[] )
> {
>        class AB *p =3D 0;
>        class A *a =3D p;
>        class B *b =3D p;
>=20
>        p->methodAB();
>        a->methodA();
>        b->methodB();
> }

Dereferencing a null pointer value to invoke a member function
results in undefined behaviour, see 1.9 para 4:

"Certain other operations are described in this International Standard=20
as undefined (for example, the effect of dereferencing the null pointer).=
"

This is deeper explained in 3.8 (Object Lifetime) p. 1. Note that the
lifetime any object of type A, B, or AB has not started before you=20
invoke the member function in main() above.

Furtheron 5.2.2 says in para 4:
"If the function is a nonstatic member function, the =93this=94 parameter=
 of=20
the function (9.3.2) shall be initialized with a pointer to the object=20
of the call, [..]"

To summarize: There exists the implied assertion, that inside a=20
non-static member function this !=3D 0.

> this program outputs:
>=20
> methodA(): this is (nil)
> methodB(): this is 0x4
> methodA(): this is (nil)
> methodB(): this is (nil)
>=20
> but I would have expected all the values to be (nil)

You cannot expect this, due to two reasons:

1) As above described, the program causes UB and such would be allowed
to print out everything it likes, even "I'll tell that your boss!"

2) Even under normal conditions there exists no guarantee that a
legal null pointer value is forced to lead to IO output of a special
value via C++ or C library (because its not demanded, that a null
pointer has an adress value of 0x0)

Greetings from Bremen,

Daniel Kr=FCgler

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: jpotter@lhup.edu (John Potter)
Date: Fri, 9 Sep 2005 03:13:41 GMT
Raw View
On Thu,  8 Sep 2005 04:20:44 GMT, jpv50@hotmail.com ("John Vincent")
wrote:

> As far as I can see from the C++ Standard, casting a null pointer to another
> pointer type should yield another null pointer. I have a small test program
> I've compiled with gcc which seems to indicate that gcc does not always
> follow this rule. Is this a gcc bug I should report to them or am I
> misunderstanding something?

You are dereferencing a null pointer and anything that happens is your
fault not that of the compiler.

> int
> main( int argc, char *argv[] )
> {
>         class AB *p = 0;
>         class A *a = p;
>         class B *b = p;

   printf("%p %p %p\n", p, a, b);

They are all null.

>
>         p->methodAB();
>         a->methodA();
>         b->methodB();

          (*b).methodB(); // Equivalent dereference of null pointer

Don't do that.  Did it format your hard drive?

John

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: bart@ingen.ddns.info (Bart van Ingen Schenau)
Date: Fri, 9 Sep 2005 03:14:51 GMT
Raw View
John Vincent wrote:

> Hi,
>
> As far as I can see from the C++ Standard, casting a null pointer to
> another pointer type should yield another null pointer. I have a small
> test program I've compiled with gcc which seems to indicate that gcc
> does not always follow this rule. Is this a gcc bug I should report to
> them or am I misunderstanding something?

What you missed is that calling a member function through a null pointer
results in undefined behaviour. Therefor, the compiler is always right.

>
> The program is and it's output follow:
>
> #include <cstdio>
>
> class A
> {
>         public:
>         void methodA();
>         private:
>         int     a;
> };
>
>
> class B
> {
>         public:
>         void methodB();
>         private:
>         int     a;
> };
>
>
> class AB: public A, public B
> {
>         public:
>         void methodAB();
>         private:
>         int     a;
> };
>
>
> void
> A::methodA()
> {
>         printf( "methodA(): this is %p\n", this );
> };
>
> void
> B::methodB()
> {
>         printf( "methodB(): this is %p\n", this );
> };
>
>
> void
> AB::methodAB()
> {
>         methodA();
>         methodB();
> }
>
> int
> main( int argc, char *argv[] )
> {
>         class AB *p = 0;
>         class A *a = p;
>         class B *b = p;
>
>         p->methodAB();

The C++ standard states that this is a syntactic equivalent of
  (*p).methodAB();
With this alternative form, it is clear that you are dereferencing a
null pointer. This results in undefined behaviour, so the compiler does
not have to take this special case into account.

>         a->methodA();
>         b->methodB();
> }
>
> this program outputs:
>
> methodA(): this is (nil)
> methodB(): this is 0x4

Clearly, the compiler adjusted the this pointer to the location where it
would normally expect the B sub-object.

> methodA(): this is (nil)
> methodB(): this is (nil)
>
> but I would have expected all the values to be (nil)
>
> Thanks in advance for any help or advice
>
> /John.
>
Bart v Ingen Schenau
--
a.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
c.l.c FAQ: http://www.eskimo.com/~scs/C-faq/top.html
c.l.c++ FAQ: http://www.parashift.com/c++-faq-lite/

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: bop@gmb.dk ("Bo Persson")
Date: Fri, 9 Sep 2005 03:14:40 GMT
Raw View
""John Vincent"" <jpv50@hotmail.com> skrev i meddelandet
news:BAY101-F7CF01FECC6B9596CC7BD2A6A60@phx.gbl...
> Hi,
>
> As far as I can see from the C++ Standard, casting a null pointer to
> another pointer type should yield another null pointer. I have a
> small test program I've compiled with gcc which seems to indicate
> that gcc does not always follow this rule. Is this a gcc bug I
> should report to them or am I misunderstanding something?
>
> The program is and it's output follow:
>
> #include <cstdio>
>
> class A
> {
>        public:
>        void methodA();
>        private:
>        int     a;
> };
>
>
> class B
> {
>        public:
>        void methodB();
>        private:
>        int     a;
> };
>
>
> class AB: public A, public B
> {
>        public:
>        void methodAB();
>        private:
>        int     a;
> };
>
>
> void
> A::methodA()
> {
>        printf( "methodA(): this is %p\n", this );
> };
>
> void
> B::methodB()
> {
>        printf( "methodB(): this is %p\n", this );
> };
>
>
> void
> AB::methodAB()
> {
>        methodA();
>        methodB();
> }
>
> int
> main( int argc, char *argv[] )
> {
>        class AB *p = 0;
>        class A *a = p;
>        class B *b = p;
>
>        p->methodAB();

You cannot call the method on a null pointer. That is undefined
behavior - the compiler can do whatever it likes.

>        a->methodA();
>        b->methodB();
> }
>
> this program outputs:
>
> methodA(): this is (nil)
> methodB(): this is 0x4

Here gcc obviously liked to print 0x4, perhaps because the B
subobjects starts 4 bytes into an AB object. Perhaps because it's
september.

> methodA(): this is (nil)
> methodB(): this is (nil)
>
> but I would have expected all the values to be (nil)

When invoking undefined behavior, you cannot expect any specific
result.


Bo Persson


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: squell@alumina.nl (Marc Schoolderman)
Date: Fri, 9 Sep 2005 03:14:42 GMT
Raw View
John Vincent wrote:

> this program outputs:
> methodA(): this is (nil)
> methodB(): this is 0x4
> methodA(): this is (nil)
> methodB(): this is (nil)
> but I would have expected all the values to be (nil)

This is ok since invoking non-static member functions through a null
pointer results in undefined behaviour. GCC assumes 'this' points to an
object and adds the base subobject's offset to it.

~Marc.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: kuyper@wizard.net
Date: Thu, 8 Sep 2005 22:14:28 CST
Raw View
"John Vincent" wrote:
> Hi,
>
> As far as I can see from the C++ Standard, casting a null pointer to another
> pointer type should yield another null pointer.

You're mostly right. Your code involves copy-initialization, which only
allows the standard conversions. Section 4.10p3 guarantees that
implicit conversion of a null derived class pointer to an unambiguous
accessible base class pointer results in a null pointer of the target
type. A pointer to an object type can be converted to a pointer to
void, but the standard fails to specify what the result is, if the
source pointer is null. The source and destination must have the same
qualifications (const/volatile). In copy-initialization, any other
pointer conversions, including conversions to an ambiguous or
inaccessible base  class pointer type, is ill-formed.

In the general case there are additional complications:

If static_cast<> is used, it may perform the same conversion as T t(e)
where T is the destination type and e is the expression being
converted, which is called direct-initialization. Per 8.5p14, this can
perform standard conversions, as described above. static_cast<> can
also perform the reverse of those conversion, and the conversion from a
pointer to an accessible unambiguous base class to a derived class
pointer is guaranteed to result in a null pointer value. As before, the
code is ill-formed if the base class is virtual, or doesn't have all of
the qualifiers (const/volatile) that the source pointer type has
(5.2.9p8).
static_cast<> can also convert void*=>T*. If the void* pointer obtained
it's null value by conversion from a T*, and the original T* was null,
then the result of converting it back will be null. Otherwise, the
standard fails to specify what the result is, if the void* value is
null.

A reinterpret_cast<> (5.2.10p8) or a const_cast<> (5.2.10p6) of a null
pointer value is guaranteed to return a null pointer value.

An explicit cast will cause some combination of const_cast<>,
static_cast<>, or reinterpret_cast<> to occur. If it calls for a
conversion that can be done by s static_cast<>, it uses the
static_cast<> to perform the cast, not reinterpret_cast<>, even if
interpreting it as a static_cast<> makes the code ill-formed. However,
an explicit cast waives the requirement that the base class be
accessible, so ambiguity of the base class is the only reason that
could come up.

Summary: there's a couple of cases where the conversion of a null
pointer is ill-formed, and a couple of cases where the standard fails
to specify what happens when a null pointer value is converted. None of
those exceptions apply to your code down below.

> I have a small test program
> I've compiled with gcc which seems to indicate that gcc does not always
> follow this rule. Is this a gcc bug I should report to them or am I
> misunderstanding something?
>
> The program is and it's output follow:
>
> #include <cstdio>
>
> class A
> {
>         public:
>         void methodA();
>         private:
>         int     a;
> };
>
>
> class B
> {
>         public:
>         void methodB();
>         private:
>         int     a;
> };
>
>
> class AB: public A, public B
> {
>         public:
>         void methodAB();
>         private:
>         int     a;
> };
>
>
> void
> A::methodA()
> {
>         printf( "methodA(): this is %p\n", this );

"%p" requires that the corresponding argument have type void*, though
this is not specified in the C++ standard itself; for functions
inherited without change from the C standard library, the C++ standard
merely cross-references the C90 standard. Since 'this' has a pointer
type and is passed as a variadic argument, no conversions are
performed, and 'this' never has the type 'void*'.  I don't have a copy
of C90, but C99 section 7.19.6.1p9 specifies that the behavior is
undefined when the format specifier doesn't match the type of the
argument provided.

> };
>
> void
> B::methodB()
> {
>         printf( "methodB(): this is %p\n", this );
> };
>
>
> void
> AB::methodAB()
> {
>         methodA();
>         methodB();
> }
>
> int
> main( int argc, char *argv[] )
> {
>         class AB *p = 0;
>         class A *a = p;
>         class B *b = p;
>
>         p->methodAB();
>         a->methodA();
>         b->methodB();

You're derefencing three null pointers. The behavior is undefined. You
should check the values of p, a, and b directly, not by looking at
'this' from within a class method.

> }
>
> this program outputs:
>
> methodA(): this is (nil)
> methodB(): this is 0x4
> methodA(): this is (nil)
> methodB(): this is (nil)

The string printed out by a "%p" format code is implementation-defined.
For all you know, 0x4 is the implementation-defined string for one of
several different null pointer representations (multiple null pointer
representations are allowed). You'll have to check your compiler's
documentation to be sure. The right way to determine whether a pointer
is null is to compare it for equality with a null pointer constant or a
pointer value of the same type, known to be null:

cout << "p:" << p==NULL << endl;
cout << "a:" << a==NULL << endl;
cout << "b:" << b==NULL << endl;

> but I would have expected all the values to be (nil)

Since there are several different reasons why the behavior of this code
is undefined, if you had any specific expectations about it's behavior,
you were wrong. It would be perfectly legal for an implementation to
translate your code in a way that resulted in printing out a long
string of obscene insults.

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Old Wolf" <oldwolf@inspire.net.nz>
Date: Sun, 11 Sep 2005 21:25:31 CST
Raw View
kuyper@wizard.net wrote:
> "John Vincent" wrote:
>> this program outputs:
>>
>> methodA(): this is (nil)
>> methodB(): this is 0x4
>> methodA(): this is (nil)
>> methodB(): this is (nil)
>
> The string printed out by a "%p" format code is implementation-
> defined. For all you know, 0x4 is the implementation-defined
> string for one of several different null pointer representations
> (multiple null pointer representations are allowed).
> You'll have to check your compiler's documentation to be sure.

That refutation is a good tool to have in your toolbox.. :)

> The right way to determine whether a pointer is null is to compare
> it for equality with a null pointer constant or a pointer value of
> the same type, known to be null:
>
> cout << "p:" << p==NULL << endl;
> cout << "a:" << a==NULL << endl;
> cout << "b:" << b==NULL << endl;

You would get more accurate results with:

  cout << "p:" << (p==NULL) << endl;

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: kuyper@wizard.net
Date: 13 Sep 2005 04:30:42 GMT
Raw View
Old Wolf wrote:
> kuyper@wizard.net wrote:
.
> > The right way to determine whether a pointer is null is to compare
> > it for equality with a null pointer constant or a pointer value of
> > the same type, known to be null:
> >
> > cout << "p:" << p==NULL << endl;
> > cout << "a:" << a==NULL << endl;
> > cout << "b:" << b==NULL << endl;
>
> You would get more accurate results with:
>
>   cout << "p:" << (p==NULL) << endl;

Sorry - I've been writing too much C, and not many bit-wise operations,
so I lost track of the precedence of "<<". :-(

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]