blog

枪手亨利

博客园 首页 新随笔 联系 订阅 管理

What static_cast<> is actually doing
http://www.codeproject.com/useritems/static_cast.asp

 

Introduction

Most programmers learn C before C++ and get use to C style casting. When writing C++, sometimes we may be confused about when to use static_cast<> and when to use reinterpret_cast<>. In this article, I will illustrate what static_cast<> is actually doing and will show some cases that will lead to errors.

Generic Types 

float f = 12.3;
float* pf = &f;

// static cast<>
int n = static_cast<int>(f);            // OK, n = 12
//int* pn = static_cast<int*>(pf);        // Error, types pointed to are unrelated
void* pv = static_cast<void*>(pf);        // OK
int* pn2 = static_cast<int*>(pv);        // OK, but *pn2 is rubbish

// reinterpret_cast<>
//int i = reinterpret_cast<int>(f);        // Error, the compiler know you should call static_cast<>
int* pi = reinterpret_cast<int*>(pf);        // OK, but *pn is actually rubbish, same as *pn2

In short, static_cast<> will try to convert, e.g. convert float-to-integer, while reinterpret_cast<> simply change the compiler's mind to reconsider that object as another type.

Pointer Types

Pointer casting are a bit complicated, we will use the following classes for the rest of the the article.

class CBaseX
{
public:
    int x;
    CBaseX() { x = 10; }
    void foo() { printf("CBaseX::foo() x=%d\n", x); }
};

class CBaseY
{
public:
    int y;
    int* py;
    CBaseY() { y = 20; py = &y; }
    void bar() { printf("CBaseY::bar() y=%d, *py=%d\n", y, *py); }
};

class CDerived : public CBaseX, public CBaseY
{
public:
    int z;
};

Case 1: Casting between unrelated classes

// Convert between CBaseX* and CBaseY*
CBaseX* pX = new CBaseX();
// CBaseY* pY1 = static_cast<CBaseY*>(pX);    // Error, types pointed to are unrelated
CBaseY* pY2 = reinterpret_cast<CBaseY*>(pX);    // Compile OK, but pY2 is not CBaseX
// pY2->bar();                    // System crash!!

As we learnt in the generic types example, static_cast<> will fail if you try to cast an object to another unrelated class, while reinterpret_cast<> will always success by "cheating" the compiler to believe that the object is really that unrelated class.

Case 2: Casting to related classes

1.  CDerived* pD = new CDerived();
2.  printf("CDerived* pD = %x\n", (int)pD);
3. 
4.  // static_cast<> CDerived* -> CBaseY* -> CDerived*
5.  CBaseY* pY1 = pD;                    // OK, implicit static_cast<> casting
6.  printf("CBaseY* pY1 = %x\n", (int)pY1);
7.  CDerived* pD1 = static_cast<CDerived*>(pY1);    // OK, now pD1 = pD
8.  printf("CDerived* pD1 = %x\n", (int)pD1);
9.  
10. // reinterpret_cast
11. CBaseY* pY2 = reinterpret_cast<CBaseY*>(pD);    // OK, but pY2 is not CBaseY*
12. printf("CBaseY* pY2 = %x\n", (int)pY2);
13. 
14. // unrelated static_cast<>
15. CBaseY* pY3 = new CBaseY();
16. printf("CBaseY* pY3 = %x\n", (int)pY3);
17. CDerived* pD3 = static_cast<CDerived*>(pY3);    // OK, even pY3 is just a "new CBaseY()"
18. printf("CDerived* pD3 = %x\n", (int)pD3);

---------------------- output ---------------------------
CDerived* pD = 392fb8
CBaseY* pY1 = 392fbc
CDerived* pD1 = 392fb8
CBaseY* pY2 = 392fb8
CBaseY* pY3 = 390ff0
CDerived* pD3 = 390fec

Noted that when static_cast<> CDerived* to CBaseY* (line 5), the result is CDerived* offset by 4. To know what static_cast<> is actually doing, we have to take a look at the memory layout of CDerived.

Memory Layout of CDerived

Class Memory Layout

As shown in the diagram, CDervied's memory layout contains two objects, CBaseX and CBaseY, and the compiler knows this. Therefore, when you cast CDerived* to CBaseY*, it adds the pointer by 4, and when you cast CBaseY to CDerived, it subtracts the pointer by 4. However, you can do this even if it is not a CDerived (line 14-18) [1].

Of course, problem happens only if you have multiple inheritance. static_cast<> and reinterpret_cast<> makes no different if you are casting between CDerived to CBaseX.

Case 3: Casting back and forth between void*

Because any pointer can be cast to void*, and void* can be cast back to any pointer (true for both static_cast<> and reinterpret_cast<>), error may occur if not handled carefully.

CDerived* pD = new CDerived();
printf("CDerived* pD = %x\n", (int)pD);

CBaseY* pY = pD;                // OK, pY = pD + 4
printf("CBaseY* pY = %x\n", (int)pY);

void* pV1 = pY;                    // OK, pV = pY
printf("void* pV1 = %x\n", (int)pV1);

CDerived* pD2 = static_cast<CDerived*>(pV1);    // pD2 = pY, but we expect pD2 = pY - 4
printf("CDerived* pD2 = %x\n", (int)pD2);
// pD2->bar();                    // System crash

---------------------- output ---------------------------
CDerived* pD = 392fb8
CBaseY* pY = 392fbc
void* pV1 = 392fbc
CDerived* pD2 = 392fbc

Once we have cast the pointer to void*, we can't cast it back to the original class easily. In the above example, the only way to get back a CDerived* from a void* is to cast it to a CBaseY* and then to CDerived*. But if we are not sure whether it is CBaseY* or CDerived*, then we  have to use dynamic_cast<> or typeid [2].

Footnote

  1. dynamic_cast<> on the other hand, can guard against casting a generic CBaseY* to CDerived*
  2. dynamic_cast<> requires the classes to be "polymorphic", i.e. contains "virtual" function, and hence can't be void*

References

History

  • 3rd February 2006: Initial version uploaded.

Sam NG


Click here to view Sam NG's online profile.


Other popular articles:

posted on 2006-02-06 09:12  henry  阅读(291)  评论(0)    收藏  举报