注意C++中物件指標,慎用void*

zenny_chen發表於2007-10-07

        由於不同C++編譯器對C++物件模型的結構可能是不一樣的。比如說,VC++系列是將虛擬函式表指標放在物件首地址,而GCC系列的編譯器則是將虛擬函式表指標放在物件地址尾部。

        那麼下面我將詳細講講GCC編譯器下的物件模型(含虛擬函式表指標)。

        先看下面這個例子:

 

#include <iostream>
using namespace std;


class A
{
private:

    
int a;
    
public:

    A(
void) : a(0)
    
{
        
    }


    
virtual void Hello(void)
    
{
        cout 
<< "Hello, world!" << endl;
    }

}
;

class B : public A
{
private:

    
int b;
    
public:

    B(
void) : b(1)
    
{
        
    }


    
virtual void Hello(void)
    
{
        cout 
<< "B!" << endl;
    }

}
;

         上面的示例中,A物件的地址中存放兩個4位元組的資料,一個是變數a,另一個是虛擬函式表指標。變數a放在低地址;虛擬函式表指標放在高地址,那麼方便起見,我這樣表示——A  objA = { a,  vptr  }。而B的物件首先是存放類A域的所有成員,然後是B自己的資料成員——B  objB = { a, vptr, b }。

        那麼這樣的安排就有助於編譯器處理這樣的情況了——

 

B b;
*= &b;
b
->Hello();

 

        那麼如果是多繼承或是虛繼承會怎樣呢?

        碰到這種情況,GCC或其相容C++編譯器會同時判斷左運算元的型別以及有運算元的型別。根據左運算元的型別來判斷左運算元的指標指向有運算元物件的哪個偏移地址。如果是其他型別的指標(如void*),則指向物件的首地址。

        下面請看一下詳細的測試程式碼:

 

#include <iostream>
using namespace std;


class A
...{
private:

    
int a;
    
public:

    A(
void) : a(0)
    
...{
        
    }


    
virtual void Hello(void)
    
...{
        cout 
<< "Hello, world!" << endl;
    }

}
;


class AA
{
private:

    
int aa;
    
public:

    AA(
void) : aa(100)
    
{
        
    }

    
    
virtual void Hi(void)
    
{
        cout 
<< "Hi, therte!" << endl;
    }

}
;


class B : public A
{
private:

    
int b;
    
public:

    B(
void) : b(1)
    
{
        
    }


    
virtual void Hello(void)
    
{
        cout 
<< "B!" << endl;
    }

}
;


class C : virtual public A
{
private:

    
int c, d;
    
public :

    C(
void) : c(2), d(3)
    
{
        
    }

    
    
void Hello(void)
    
{
        cout 
<< "C!" << endl;
    }

}
;


class E : public A, public AA
{
private:

    
int e;
    
public:

    E(
void) : e(8)
    
{
        
    }

    
    
void Hello(void)
    
{
        cout 
<< "E!" << endl;
    }

    
    
void Hi(void)
    
{
        cout 
<< "Hi!" << endl;
    }

}
;


extern "C" void test(void);


int main(void)
{
    A a;
    B b;
    C c;
    E e;
    
    unsigned 
long s[10];
    
    
int i=0;
    
    
for(; i<sizeof(a) >> 2; i++)
        s[i] 
= ((unsigned long*)&a)[i];
        
    a.Hello();
    
    
for(i=0; i<sizeof(b) >> 2; i++)
        s[i] 
= ((unsigned long*)&b)[i];
        
    A 
*= &b;
    
    p
->Hello();
    
    
for(i=0; i<sizeof(c) >> 2; i++)
        s[i] 
= ((unsigned long*)&c)[i];
        
    p 
= &c;
    
    
void* q = (C*)&c;
    
    p
->Hello();
    
    
for(i=0; i<sizeof(e) >> 2; i++)
        s[i] 
= ((unsigned long*)&e)[i];
        
    AA 
*pp = &e;
    p 
= &e;
    q 
= &e;
    
    
return 0;
}

 

        在main函式中,上面的p和q兩個指標的值是不同的,儘管它們指向同一個物件。p指向了物件c的A類域的偏移處;而q則是指向了p 的首地址。那麼下面的pp和p及q也是不同,這裡的p和q都是指向首地址,因為類A域在物件e的起始處,而pp則是指向了e的AA類域的偏移處。

        大家可以利用以上程式碼進行除錯,有個感性認識。

        所以在C++中,甚用void*指標指向一個物件,否則當再次進行型別轉換時,呼叫相關函式可能會發生意想不到的情況。那麼這個時候還是利用模板,通過範型來解決型別問題,這樣做更安全,而且更優美。

相關文章