Topic: casting class function addresses


Author: kanze@us-es.sel.de (James Kanze)
Date: 18 Apr 1994 18:23:21 GMT
Raw View
In article <jpcauvin-080494214617@slip-6-3.ots.utexas.edu>
jpcauvin@bongo.cc.utexas.edu (Roger L. Cauvin) writes:

|> In article <9404081933.AA22782@ses.com>, jamshid@ses.com (Jamshid Afshar)
|> wrote:

|> ...

|> > JA>[...] Some current C++ compilers can optionally, at the
|> > JA>expense of letting ptm casts work, optimize the size of ptms.  It's
|> > JA>still very uncertain whether ANSI/ISO will allow this optimization [...]

|> ...

|> If *no* pointer to member function casts are guaranteed to work on these
|> compilers, then they are irrelevant to this discussion.  We have already
|> established that the ARM explicitly states that *some* pointer to member
|> function casting is allowed.

But not guaranteed to work:-).

What is, I think, fairly established, is that you can cast a pointer
to member to a pointer to member of a different class within the
hierarchy, then cast it back to the original type, and it will work.
I fail to find text in the ARM guaranteeing even this, much less
anything more, although the limitations concerning unambiguous
derivation (in section 5.4) are highly suggestive that more was meant.

|> I recognize the possibility that peculiarities in a compiler's
|> implementation of member function pointers could effect the reliability of
|> casting and dereferencing member function pointers.  The question is
|> whether such compiler peculiarities are ruled out, directly or indirectly,
|> by the ARM or ANSI/ISO.

For the moment, they are not clearly ruled out, so we have to consider
the probability that some implementor will have used them.

|> > Besides, my paragraph you quote above was in reference to your code:

|> >  typedef void (A::*AFP)();
|> >  typedef void (B::*BFP)();  // class B unrelated to A
|> >  void g( AFP a ) {
|> >     BFP bfp = *(BFP*)&a;   // definitely undefined run-time behavior
|> >  }

|> > Alignment requirements aside, nowhere does the ARM imply that this
|> > might work.  You can't treat a block of memory containing an object
|> > like it's a block of memory containing a different type of object.

|> You keep saying this (and giving exceptions), but you still haven't
|> provided compelling evidence that such a technique will not work in the
|> case of member function pointers.

Is the fact that it won't work with any of the compilers I currently
use compelling evidence?  It will work some of the time.  It will in
fact always work if there is no multiple inheritance in either of the
classes involved.  It will also work if neither of the pointers points
to an inherited member.  (The above holds for Sun CC, a cfront
variant.  The other compilers are somewhat different; at least one
simply forbids all casts of pointers to members.)

|> > I think there's one exception to this besides chars: the address of an
|> > aggregate is guaranteed to be the address of the first member:

|> >  struct S { int i; } s;
|> >  int* p = (int*)(void*)&s;  // okay: p points to s.i
|> >  int a[10];
|> >  int* q = (int*)(void*)&a;  // okay: q points to a[0]

|> > Note that the layout of non-aggregate classes is undefined:

|> >  struct D : public S {};
|> >  D d;
|> >  int* p = (int*)(void*)&d;  // bad: (void*)&d is not necessarily equal
|> >                             // to (S*)&d

|> By the way, there's no need to cast the addresses to (void *) before
|> casting them to (int *).

But it is good style.  In the absense of the new cast syntax, a cast
to void* is about the best way I know of of ensuring that a
reinterpret_cast is involved, and of letting the reader know that this
is intentional.
--
James Kanze                       email: kanze@lts.sel.alcatel.de
GABI Software, Sarl., 8 rue du Faisan, F-67000 Strasbourg, France
Conseils en informatique industrielle --
                   -- Beratung in industrieller Datenverarbeitung




Author: jamshid@ses.com (Jamshid Afshar)
Date: 4 Apr 1994 12:57:13 -0500
Raw View
Redirected to comp.std.c++.

In article <jpcauvin-020494115637@slip-15-2.ots.utexas.edu>,
Roger L. Cauvin <jpcauvin@bongo.cc.utexas.edu> wrote:
>In article <BWH.94Apr2122342@beach.cis.ufl.edu>, bwh@beach.cis.ufl.edu
>(Brian Hook) wrote:
>: The reason that:
>:    int i = 2;
>;    double d = *(double *)&i;
>; Will fail is that values for doubles are stored differently than values
>: for ints, not just because of size differences.  If the above used "float"
>; instead of "double" and sizeof( int ) == sizeof( float ) it would still
>: fail because the bits within i would be interpreted incorrectly.

That is correct.  Imagine a floating point math processor which
signaled an error when an invalid bit-pattern was detected.  `d' might
actually be kept in one of the math processor's registers so running
the above code might crash the program, just like dereferencing a null
pointer might.

>Of *course* a coercion of double to int will not result in an intuitive
>conversion.  However, this is a far cry from the coercion causing a
>run-time error.
>Casting, and to an even greater extent, coercion, are fraught with dangers
>of losing significant bits, producing unintuitive conversions, and
>overwriting data.  Nonetheless, a simple coercion of one pointer type to
>another should not *itself* cause a run-time error.  What is done with the
>pointer *afterward* may.

What do you mean by "simple coercion of one pointer type to another"?
Where did you learn a distinction between the terms "coercion" and
"casting"?  K&R1, CPL2, and the June 1993 ANSI/ISO Working Paper all
seem to use the terms interchangeably.

Anyway, the above code definitely results in undefined behavior (eg,
run-time error).  You may cast any pointer to *object* (ie, NOT
pointers to members or pointers to regular functions) to another
pointer to object type.  While this pointer conversion may compile it
may crash at run-time if the object pointed to is not suitably
aligned, and alignment requirements are implementation-dependent.

 int i=2;
 double* dp = (double*)&i;  // even if you do nothing with dp, the
                            // program can crash at this line

Imagine a 32-bit CPU which had a special register to store pointers to
objects which are aligned on a double word boundary.  The CPU might
crash when a value with non-zero least significant bits was stored in
the register.  The compiler might always align doubles on a double
words, but `i' might not be so aligned (even if it's the same size).

A pointer TO a pointer to a member may be converted to another pointer
to object type; after all, it is a pointer to object.  But like the
double* conversion above, such a conversion may result in a crash if
pointers to members have different alignment requirements (ie, are of
different sizes).  Some current C++ compilers can optionally, at the
expense of letting ptm casts work, optimize the size of ptms.  It's
still very uncertain whether ANSI/ISO will allow this optimization
(see the "Casting member function pointers" thread in comp.std.c++).
So, it's unclear whether the following code will result in undefined
behavior:

 typedef void (A::*AFP)();
 typedef void (B::*BFP)();
 void f( AFP a ) {
    BFP* bfpp = (BFP*)&a;
 }

But, I think all this is pretty much moot because your code (in your
first article) actually dereferenced the pointer to pointer to member:

 void g( AFP a ) {
    BFP bfp = *(BFP*)&a;   // definitely undefined run-time behavior
 }

You can't just treat the block of memory where a particular object is
stored like a block of memory containing another type of object (chars
being an exception).  I don't think it matters whether you even use
bfp, just like it doesn't matter whether the `d' from this article's
first example is used.

Jamshid Afshar
jamshid@ses.com





Author: jpcauvin@bongo.cc.utexas.edu (Roger L. Cauvin)
Date: 5 Apr 1994 03:00:27 GMT
Raw View
In article <9404041757.AA28726@ses.com>, jamshid@ses.com (Jamshid Afshar)
wrote:

<Much stuff deleted about casting/coercing member function pointers>

> You can't just treat the block of memory where a particular object is
> stored like a block of memory containing another type of object (chars
> being an exception).

You can if the objects are of the same size and alignment.  If you can
prove that not all member function pointers have the same size and
alignment (an unlikely implementation even if theoretically possible), then
I will concede that coercion of member function pointers *could* result in
a run-time error in *some* compiler implementations.

Roger



P.S.  I picked up the distinction between casting and coercion from a C
book a while back.  I don't remember which book it was, though.  While
casting is a relatively safe, built-in means of conversion, coercion is a
conversion achieved by casting the address of an object and dereferencing
it.




Author: jamshid@ses.com (Jamshid Afshar)
Date: 8 Apr 1994 14:33:19 -0500
Raw View
In article <jpcauvin-040494213746@slip-1-49.ots.utexas.edu>,
Roger L. Cauvin <jpcauvin@bongo.cc.utexas.edu> wrote:
>In article <9404041757.AA28726@ses.com>, jamshid@ses.com (Jamshid Afshar)
>wrote:
>> You can't just treat the block of memory where a particular object is
>> stored like a block of memory containing another type of object (chars
>> being an exception).
>
>You can if the objects are of the same size and alignment.  If you can
>prove that not all member function pointers have the same size and
>alignment (an unlikely implementation even if theoretically possible), then
>I will concede that coercion of member function pointers *could* result in
>a run-time error in *some* compiler implementations.

You either missed or didn't agree with some of the things I wrote in
the article to which you responded.  Here it is again:

JA>[...] Some current C++ compilers can optionally, at the
JA>expense of letting ptm casts work, optimize the size of ptms.  It's
JA>still very uncertain whether ANSI/ISO will allow this optimization [...]

Borland has offered this optimization for at least a couple of years
(in fact, I seem to remember it being the default option a few
versions ago).  I believe Microsoft also offers this optimization.
Basicly, the pointer to member example implementation in ARM 8.1.2c
can be smaller if there's no need to store the `delta' member needed
by multiple inheritance.  I'm not saying that it's likely that ptms
will have different alignment requirements, just that it might end up
being legal under ANSI/ISO C++.

Besides, my paragraph you quote above was in reference to your code:

 typedef void (A::*AFP)();
 typedef void (B::*BFP)();  // class B unrelated to A
 void g( AFP a ) {
    BFP bfp = *(BFP*)&a;   // definitely undefined run-time behavior
 }

Alignment requirements aside, nowhere does the ARM imply that this
might work.  You can't treat a block of memory containing an object
like it's a block of memory containing a different type of object.  I
think there's one exception to this besides chars: the address of an
aggregate is guaranteed to be the address of the first member:

 struct S { int i; } s;
 int* p = (int*)(void*)&s;  // okay: p points to s.i
 int a[10];
 int* q = (int*)(void*)&a;  // okay: q points to a[0]

Note that the layout of non-aggregate classes is undefined:

 struct D : public S {};
 D d;
 int* p = (int*)(void*)&d;  // bad: (void*)&d is not necessarily equal
                            // to (S*)&d

>P.S.  I picked up the distinction between casting and coercion from a C
>book a while back.  I don't remember which book it was, though.  While
>casting is a relatively safe, built-in means of conversion, coercion is a
>conversion achieved by casting the address of an object and dereferencing
>it.

It's good that you understand that casting is a conversion, not just
"reinterprit these bits as a...", but like I wrote earlier, K&R 1 and
2 explicitly use the terms casting and coercion as synonyms.  I've
never seen them distinguished in any C or C++ books or articles I've
read.  You're of course free to use whatever language you like, but
don't be surprised if others don't understand you.

Jamshid Afshar
jamshid@ses.com




Author: jpcauvin@bongo.cc.utexas.edu (Roger L. Cauvin)
Date: 9 Apr 1994 03:23:21 GMT
Raw View
In article <9404081933.AA22782@ses.com>, jamshid@ses.com (Jamshid Afshar)
wrote:

...

> JA>[...] Some current C++ compilers can optionally, at the
> JA>expense of letting ptm casts work, optimize the size of ptms.  It's
> JA>still very uncertain whether ANSI/ISO will allow this optimization [...]

...

If *no* pointer to member function casts are guaranteed to work on these
compilers, then they are irrelevant to this discussion.  We have already
established that the ARM explicitly states that *some* pointer to member
function casting is allowed.

I recognize the possibility that peculiarities in a compiler's
implementation of member function pointers could effect the reliability of
casting and dereferencing member function pointers.  The question is
whether such compiler peculiarities are ruled out, directly or indirectly,
by the ARM or ANSI/ISO.

> Besides, my paragraph you quote above was in reference to your code:
>
>  typedef void (A::*AFP)();
>  typedef void (B::*BFP)();  // class B unrelated to A
>  void g( AFP a ) {
>     BFP bfp = *(BFP*)&a;   // definitely undefined run-time behavior
>  }
>
> Alignment requirements aside, nowhere does the ARM imply that this
> might work.  You can't treat a block of memory containing an object
> like it's a block of memory containing a different type of object.

You keep saying this (and giving exceptions), but you still haven't
provided compelling evidence that such a technique will not work in the
case of member function pointers.

> I think there's one exception to this besides chars: the address of an
> aggregate is guaranteed to be the address of the first member:
>
>  struct S { int i; } s;
>  int* p = (int*)(void*)&s;  // okay: p points to s.i
>  int a[10];
>  int* q = (int*)(void*)&a;  // okay: q points to a[0]
>
> Note that the layout of non-aggregate classes is undefined:
>
>  struct D : public S {};
>  D d;
>  int* p = (int*)(void*)&d;  // bad: (void*)&d is not necessarily equal
>                             // to (S*)&d

By the way, there's no need to cast the addresses to (void *) before
casting them to (int *).

...

> It's good that you understand that casting is a conversion, not just
> "reinterprit these bits as a...", but like I wrote earlier, K&R 1 and
> 2 explicitly use the terms casting and coercion as synonyms.  I've
> never seen them distinguished in any C or C++ books or articles I've
> read.  You're of course free to use whatever language you like, but
> don't be surprised if others don't understand you.

I never claimed to be surprised.

Roger