casting A* to A& ?

A

Agoston Bejo

What happens exactly when I do the following:

struct A {
int i;
string j;
A() {}
};

void f(A& a) {
cout << a.i << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
A a;
a.i = 6;
A* pa = &a;
f((A&)pa); // CRITICAL POINT
f(*pa); // works as expected
return 0;
}


How come that this is legal, anyway?
Lately I've seen some people expect that (A&)pa be equivalent to *pa, which
is not according to the example above. (The output for f((A&)pa) looks like
some integer, but surely not 6 as should be if it meant f(*pa).)
 
S

Sigurd Stenersen

Agoston said:
What happens exactly when I do the following:

struct A {
int i;
string j;
A() {}
};

void f(A& a) {
cout << a.i << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
A a;
a.i = 6;
A* pa = &a;
f((A&)pa); // CRITICAL POINT
f(*pa); // works as expected
return 0;
}


How come that this is legal, anyway?

You can cast something at an address to something else at the same address.
Try doing the same cast with a constant and you'll see that that is not
allowed.

Lately I've seen some people expect that (A&)pa be equivalent to *pa,
which is not according to the example above.

No, it is not. *pa is a reference to whatever pa is pointing to, tho. So
f(*pa);
is perfectly fine.

(The output for
f((A&)pa) looks like some integer, but surely not 6 as should be if
it meant f(*pa).)

The integer is equal to the address of a.

What actually happens is you're telling the compiler that "look here, pa is
not a pointer it's a struct of type A". That is in fact wrong, but because
you're telling the compiler that you know better it let's you do it. And
you're lucky, because the size of pa happens to be the same as the size of
an A instance. As it happens, the value of p overlaps A::i when you do the
cast.

Thus, you only get bogus results in this simple example, in reality you'd
usually get an access violation or some other hardtofind bug.


In other words, when you use a typecast, you're telling the compiler that
you know what you're doing. And that's a very silly thing to tell the
compiler if you don't know what you're doing...


(Yes, references are implemented the same way as pointers. But they have
different semantics.)
 
A

Agoston Bejo

Thanks for the thorough answer!



[...]
What actually happens is you're telling the compiler that "look here, pa is
not a pointer it's a struct of type A". That is in fact wrong, but because
you're telling the compiler that you know better it let's you do it. And

So does (Type)var mean the same as reinterpret_cast<Type>(var)?
you're lucky, because the size of pa happens to be the same as the size of
an A instance. As it happens, the value of p overlaps A::i when you do the
cast.

I think the first statement (about the sizes) is not really relevant, what
matters is that the size of an A instance is at least as big as the size of
pa, so no out-of-heap memory is read which could cause access violation. Am
I right?

However, I've tried it with A having only a char member and nothing else,
but no access violation occured in this case, either. (I compiled the
example both in debug and release mode.)
I think it should have produced some runtime error, especially in debug
mode, shouldn't it?
 
V

Vladimir Nesterovsky

Hello,
What happens exactly when I do the following:

struct A {
int i;
string j;
A() {}
};

void f(A& a) {
cout << a.i << endl;
}

int _tmain(int argc, _TCHAR* argv[])
{
A a;
a.i = 6;
A* pa = &a;
f((A&)pa); // CRITICAL POINT
f(*pa); // works as expected
return 0;
}


How come that this is legal, anyway?
Lately I've seen some people expect that (A&)pa be equivalent to *pa, which
is not according to the example above. (The output for f((A&)pa) looks like
some integer, but surely not 6 as should be if it meant f(*pa).)

If you would used C++ style cast notation.
The only valid code would be: f(reinterpret_cast<A&>(pa)); // A* & -> A &.

When you see *_cast operators in code, this often means that program could
be written better. But when you see reinterpret_cast, this usually means
that something is wrong, unless it's low level code.
 
S

Sigurd Stenersen

Agoston said:
[...]
What actually happens is you're telling the compiler that "look
here, pa is not a pointer it's a struct of type A". That is in fact
wrong, but because you're telling the compiler that you know better
it let's you do it. And

So does (Type)var mean the same as reinterpret_cast<Type>(var)?

I *think* so. I don't use that sort of cast myself.

Or is it context-dependent, how C-style casts are interpreted?
No.



I think the first statement (about the sizes) is not really relevant,

Oh, yes, it is *highly* relevant.

Consider this :

struct A
{
// char s[100000];
int i;
};

void f(A& a)
{
cout << a.i << endl;
}

void main()
{
A *pa = new A;
f((A&) pa);
}

That will be bogus but still "work". Now, remove the // from the third line
and guess what happens ?

Also, consider your original code and just change the cout statement to
cout << a.i << a.j.c_str() << endl;
and you achieve pretty much the same result...

what matters is that the size of an A instance is at least as big as
the size of pa, so no out-of-heap memory is read which could cause
access violation. Am I right?

No, it's the other way around. As long as the variable that's actually used
(pa) is big enough, at least you wont corrupt other variables. Still, the
results will be bogus at best.

However, I've tried it with A having only a char member and nothing
else, but no access violation occured in this case, either. (I
compiled the example both in debug and release mode.)
I think it should have produced some runtime error, especially in
debug mode, shouldn't it?

No, it's *legal*. You told the compiler you knew what you were doing...
 
T

Tom Widmer

Thanks for the thorough answer!



[...]
What actually happens is you're telling the compiler that "look here, pa is
not a pointer it's a struct of type A". That is in fact wrong, but because
you're telling the compiler that you know better it let's you do it. And

So does (Type)var mean the same as reinterpret_cast<Type>(var)?
I thought it meant static_cast<Type>(var).
Or is it context-dependent, how C-style casts are interpreted?

It is context dependent. A C-style cast can act as a static_cast, a
const_cast or a reinterpret_cast, as well as combinations of those. It
can also perform casts that can't even be done by reinterpret_cast,
such as casting to a private base class.
I think the first statement (about the sizes) is not really relevant, what
matters is that the size of an A instance is at least as big as the size of
pa, so no out-of-heap memory is read which could cause access violation. Am
I right?

The important thing is that your function f only accesses the first 4
bytes of the passed object, so it only accesses the 4 bytes of the
passed pointer value, giving out a garbage int value that is the value
of the passed pointer.

However, the code has undefined behaviour.
However, I've tried it with A having only a char member and nothing else,
but no access violation occured in this case, either. (I compiled the
example both in debug and release mode.)

Try instead changing your function to print out a.j rather than a.i.
That will likely cause a proper crash, since it will treat the bytes
of stack space after the passed pointer as a valid string object, when
in reality they probably contain random byte values.
I think it should have produced some runtime error, especially in debug
mode, shouldn't it?

Undefined behaviour often doesn't produce any obvious results, rather
it corrupts the heap or stack, causing your program to produce
incorrect results or crash further down the line. It you're very lucky
it crashes at the point of the error, but this is quite rare.

Tom
 
A

Alexander Grigoriev

C-style cast are interpreted either as static_cast (if a standard conversion
exists), or reinterpret_cast otherwise.

Sigurd Stenersen said:
So does (Type)var mean the same as reinterpret_cast<Type>(var)?

I *think* so. I don't use that sort of cast myself.

Or is it context-dependent, how C-style casts are interpreted?
No.



I think the first statement (about the sizes) is not really relevant,

Oh, yes, it is *highly* relevant.

Consider this :

struct A
{
// char s[100000];
int i;
};

void f(A& a)
{
cout << a.i << endl;
}

void main()
{
A *pa = new A;
f((A&) pa);
}

That will be bogus but still "work". Now, remove the // from the third
line
and guess what happens ?

Also, consider your original code and just change the cout statement to
cout << a.i << a.j.c_str() << endl;
and you achieve pretty much the same result...

what matters is that the size of an A instance is at least as big as
the size of pa, so no out-of-heap memory is read which could cause
access violation. Am I right?

No, it's the other way around. As long as the variable that's actually
used
(pa) is big enough, at least you wont corrupt other variables. Still, the
results will be bogus at best.

However, I've tried it with A having only a char member and nothing
else, but no access violation occured in this case, either. (I
compiled the example both in debug and release mode.)
I think it should have produced some runtime error, especially in
debug mode, shouldn't it?

No, it's *legal*. You told the compiler you knew what you were doing...


--


Sigurd
http://utvikling.com
 
S

Sigurd Stenersen

Tom said:
Thanks for the thorough answer!


[...]
What actually happens is you're telling the compiler that "look
here, pa is not a pointer it's a struct of type A". That is in
fact wrong, but because you're telling the compiler that you know
better it let's you do it. And

So does (Type)var mean the same as reinterpret_cast<Type>(var)?
I thought it meant static_cast<Type>(var).
Or is it context-dependent, how C-style casts are interpreted?

It is context dependent. A C-style cast can act as a static_cast, a
const_cast or a reinterpret_cast, as well as combinations of those. It
can also perform casts that can't even be done by reinterpret_cast,
such as casting to a private base class.

It does the same thing no matter what the context is - it treats something
of one type like it actually is some other type.

Casting to certain types results in the creation of a temporary object of
the destination type. However, that's not related to context but rather
it's related to the destination type.
 
S

Sigurd Stenersen

Alexander said:
C-style cast are interpreted either as static_cast (if a standard
conversion exists), or reinterpret_cast otherwise.

So ? C-style casts work the same way in any context. You could argue that
there is one exception (I mentioned this in my last post), but that's
depending on the target type rather than on the context and it's not really
a cast anyway. It just looks like one.
 
C

Carl Daniel [VC++ MVP]

Sigurd said:
It does the same thing no matter what the context is - it treats
something of one type like it actually is some other type.

Tom is correct. According to 5.4/5 and 5.4/7 of the C++ Standard:

5 The conversions performed by
- a const_cast (5.2.11),
- a static_cast (5.2.9),
- a static_cast followed by a const_cast,
- a reinterpret_cast (5.2.10), or
- a reinterpret_cast followed by a const_cast,
can be performed using the cast notation of explicit type conversion. The
same semantic restrictions and behaviors apply. If a conversion can be
interpreted in more than one of the ways listed above, the interpretation
that appears first in the list is used, even if a cast resulting from that
interpretation is illformed. If a conversion can be interpreted in more than
one way as a static_cast followed by a const_cast, the conversion is
illformed.

[Example:

struct A {};
struct I1 : A {};
struct I2 : A {};
struct D : I1, I2 {};
A *foo( D *p ) {
return (A*)( p ); // illformed static_cast interpretation
}

-end example]

7 In addition to those conversions, the following static_cast and
reinterpret_cast operations (optionally followed by a const_cast operation)
may be performed using the cast notation or explicit type conversion, even
if the base class type is not accessible:
- a pointer to an object of derived class type or an lvalue of derived class
type may be explicitly converted to a pointer or reference to an unambiguous
base class type, respectively;
- a pointer to member of derived class type may be explicitly converted to a
pointer to member of an unambiguous nonvirtual base class type;
- a pointer to an object of nonvirtual base class type, an lvalue of
nonvirtual base class type, or a pointer to member of nonvirtual base class
type may be explicitly converted to a pointer, a reference, or a pointer to
member of a derived class type, respectively.


-cd
 
L

Larry Brasfield

Sigurd Stenersen said:
So ? C-style casts work the same way in any context. You could argue that
there is one exception (I mentioned this in my last post), but that's
depending on the target type rather than on the context and it's not really
a cast anyway. It just looks like one.

You've made a distinction I cannot follow, and I believe
it is incorrect as well. The 'target type' certainly must be
considered part of the context of an expression forming
the right-hand side of an assignment.

The very reason that the old C casts should be avoided
is because they can do different things depending on
context. For example, consider this cast:
x_ptr = (SomeType *)p;
Is is a static_cast or a reinterpret_cast? You cannot
tell until we know the types of both x_ptr and p.
Given these declarations:
class Base {} *p;
class Derv : public Base {} *x_ptr;
then it was a static_cast.
Given these declarations:
class Apple {} *p;
class Orange {} *x_ptr;
then it was a reinterpret_cast.

What makes the C cast so insidious is that its operation
can change as a side effect of changes in code far
removed from the location of the cast. If not for that
misfeature, (if they were as stable as you claim), they
would be safer to use. A static_cast would always
remain a converstion between compatible types,
(and a reinterpret_cast would remain as dangerous
as ever).
 
S

Sigurd Stenersen

Carl said:
Tom is correct.

I didn't say he's wrong. I pointed out that the C-style cast does the same
thing no matter what you use it for. The fact that you have three different
ways to do a C++ style cast, with different restrictions for each one, does
not change the way the C-style cast works...
 
S

Sigurd Stenersen

Larry said:
You've made a distinction I cannot follow, and I believe
it is incorrect as well.

It's not *that* hard... Given
char *p = "...";
(std::string) p;
you're doing something more than just changing the "compiler perceived"
type. My point is that this is not really a cast, it's an instantiation
exactly the same way that
char *p = "...";
std::string(p);
is an instantiation and not a cast.

The case is not the same for actual typecasts that use the same notation,
e.g.
int i;
(char) i;
is the same as
int i;
char(i);
which is a cast and not an instantiation.

The 'target type' certainly must be
considered part of the context of an expression forming
the right-hand side of an assignment.

In that case we have different ideas about the meaning of the word
"context". I guess I could be wrong, english is not my native language.
 
T

Tom Widmer

Tom said:
Thanks for the thorough answer!


[...]

What actually happens is you're telling the compiler that "look
here, pa is not a pointer it's a struct of type A". That is in
fact wrong, but because you're telling the compiler that you know
better it let's you do it. And

So does (Type)var mean the same as reinterpret_cast<Type>(var)?
I thought it meant static_cast<Type>(var).
Or is it context-dependent, how C-style casts are interpreted?

It is context dependent. A C-style cast can act as a static_cast, a
const_cast or a reinterpret_cast, as well as combinations of those. It
can also perform casts that can't even be done by reinterpret_cast,
such as casting to a private base class.

It does the same thing no matter what the context is - it treats something
of one type like it actually is some other type.

By context I mean the combination of source type and destination type.
Without knowing both of those types, you can't tell what conversion is
going to be performed.

Depending on the relationship, the cast might cause a function to be
called, might need a cast pointer value to be adjusted (multiple
inheritence), or it might not need to do anything at runtime.

const_cast, static_cast and reinterpret_cast give you more control,
since each of them will only do a subset of what a C-style cast can
do.
Casting to certain types results in the creation of a temporary object of
the destination type.

All casts to non-reference types give you a temporary (or rvalue),
unless I'm mistaken.
However, that's not related to context but rather
it's related to the destination type.

The point is that what the cast dose is related to both source and
destination type, and the relationship between those types.

Tom
 
I

Igor Tandetnik

Sigurd Stenersen said:
It does the same thing no matter what the context is - it treats
something of one type like it actually is some other type.

You seem to argue that a C-style cast is always the same as
reinterpret_cast - that is, it just takes bits in memory, and interprets
these bits as a representation of a different type. This is not always
so. Consider:

class A {int a;};
class B {int b;};

class C : public A, public B {};

C c;
C* pC = &c;

B* pB1 = static_cast<B*>(pC);

Here, the physical value of pB1 is very likely different from that of pC
(that is, (void*)pB1 != (void*)pC ), because B subobject is at a
non-zero offset inside C (a zero offset is most likely occupied by A
subobject).

B* pB2 = reinterpret_cast<B*>(pC);

Here, the physical value of pB2 is the same as pC (that's the whole
point of reinterpret_cast). Incidentally, pB2 does not point to a valid
object of type B. Dereferencing it would cause undefined behavior.

B* pB3 = (B*)pC;

Here, the C-style cast behaves the same way as static_cast, not as
reinterpret_cast. pB3 == pB1 and is different from pC.

class D {};
D* pD = (D*)pC;

Here, C-style cast behaves like reinterpret_cast - the physical value of
pD is the same as pC.

That's what people mean when they say C-style cast is
context-sensitive - it behaves differently when casting between related
types than when casting between unrelated ones; it takes the relation
between the two types into account. Contrast this with reinterpret_cast,
which just blindly takes the bit layout as is, and treats it as a
different type.
--
With best wishes,
Igor Tandetnik

"On two occasions, I have been asked [by members of Parliament], 'Pray,
Mr. Babbage, if you put into the machine wrong figures, will the right
answers come out?' I am not able to rightly apprehend the kind of
confusion of ideas that could provoke such a question." -- Charles
Babbage
 
T

Tom Widmer

No, it's *legal*. You told the compiler you knew what you were doing...

To put it more precisely, the code is well-formed but exhibits
undefined behaviour.

Tom
 
I

Igor Tandetnik

Agoston Bejo said:
int _tmain(int argc, _TCHAR* argv[])
{
A a;
a.i = 6;
A* pa = &a;
f((A&)pa); // CRITICAL POINT

This is undefined behavior, so there are no rules on how the compiler
should interpret it. I believe VC++ would treat it the same way as

f( *(A*)&pa );

That is, the memory occupied by pa pointer is treated as if it held A
object. Inside f, a.i would likely be equal to (int)pa
--
With best wishes,
Igor Tandetnik

"On two occasions, I have been asked [by members of Parliament], 'Pray,
Mr. Babbage, if you put into the machine wrong figures, will the right
answers come out?' I am not able to rightly apprehend the kind of
confusion of ideas that could provoke such a question." -- Charles
Babbage
 
C

Carl Daniel [VC++ MVP]

Sigurd said:
I didn't say he's wrong. I pointed out that the C-style cast does
the same thing no matter what you use it for.

That depends on how you interpret "does the same thing". Sometimes a
C-style cast will result in a change of value representation (e.g. cast from
base to derived) and sometimes it wont.

For example, if you wanted to cast from base to derived but prevent any
change in value representation (this-pointer adjustments), you'd have to use
a reinterpret_cast, because in that context a C-style cast will behave as a
static_cast.

Contrast this to C where casts between any two pointer types are the
equivalent of a reinterpret_cast.

-cd
 
T

Tom Widmer

I didn't say he's wrong. I pointed out that the C-style cast does the same
thing no matter what you use it for.

C-style casting to, say, a "std::string const&" might involve a
constructor call, a conversion operator call, a reference coercion, a
qualification adjustment or a no-op, depending on the source type. I
don't consider all of those to be the same thing!

Tom
 
S

Sigurd Stenersen

Carl said:
That depends on how you interpret "does the same thing". Sometimes a
C-style cast will result in a change of value representation (e.g.
cast from base to derived)

No, that's when you're instantiating rather than casting.

For example, if you wanted to cast from base to derived but prevent
any change in value representation (this-pointer adjustments), you'd
have to use a reinterpret_cast, because in that context a C-style
cast will behave as a static_cast.

Yeah. And now you're talking about something that's just not possible to do
with a C-style cast.

Contrast this to C where casts between any two pointer types are the
equivalent of a reinterpret_cast.

Yes, that is one of the differences between C-style and C++ style casts.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top