不當使用memset函數(shù)帶來的麻煩問題

字號:

通常在C的編程中,我們經(jīng)常使用memset函數(shù)將一塊連續(xù)的內(nèi)存區(qū)域清零或設置為其它指定的值,最近在移植一段java代碼到C++的時候,不當使用memset函數(shù)花費了我?guī)讉€小時的調(diào)試時間??荚嚧筇崾? 對于虛函數(shù)的底層機制很多資料都有較詳細闡述,這次的調(diào)試感觸頗深。
    先來看一段代碼,在繼承的類Advance之中,有很多屬性字段,Examda希望將其清成0或NULL,于是在構(gòu)造函數(shù)中Examda通過memset將當前類的所有屬性置0。
    class Base{
    public:
    virtual void kickoff() = 0;
    };
    class Advance:public Base{
    public:
    Advance(){
    memset(this, 0, sizeof(Advance));
    }
    void kickoff(){
    count++;
    //... do something else;
    }
    private:
    int attr1, attr2;
    char* label;
    int count;
    //... other attributes, they should be initiated to 0 or NULL at beginning.
    };
    int _tmain(int argc, _TCHAR* argv[])
    {
    Base* ptr = new Advance();
    ptr->kickoff();
    return 0;
    }
    這樣看似能正常運行,但運行程序時,你會發(fā)現(xiàn)類似于下面的錯誤:
    TestVirtual.exe 中的 0x00415390 處未處理的異常: 0xC0000005: 讀取位置 0x00000000 時發(fā)生訪問沖突
    同時斷點停留在ptr->kickoff()處,從錯誤提示我們可以得知無法調(diào)用kickoff方法,這個方法的指針沒有被正確初始化,但為什么呢?
    指出問題之前,先看看這段文獻上的關(guān)于虛函數(shù)機制的說明:
    函數(shù)賴以生存的底層機制:vptr + vtable。虛函數(shù)的運行時實現(xiàn)采用了VPTR/VTBL的形式,這項技術(shù)的基礎(chǔ):
    ①編譯器在后臺為每個包含虛函數(shù)的類產(chǎn)生一個靜態(tài)函數(shù)指針數(shù)組(虛函數(shù)表),在這個類或者它的基類中定義的每一個虛函數(shù)都有一個相應的函數(shù)指針。
    ②每個包含虛函數(shù)的類的每一個實例包含一個不可見的數(shù)據(jù)成員vptr(虛函數(shù)指針),這個指針被構(gòu)造函數(shù)自動初始化,指向類的vtbl(虛函數(shù)表)
    ③當客戶調(diào)用虛函數(shù)的時候,編譯器產(chǎn)生代碼反指向到vptr,索引到vtbl中,然后在指定的位置上找到函數(shù)指針,并發(fā)出調(diào)用。
    這里的問題,就出在
    memset(this, 0, sizeof(Advance));
    上面,虛函數(shù)指針應該在進入構(gòu)造函數(shù)賦值體之前自動初始化的,而memset卻又將已經(jīng)初始化好的指針清0了,這就是為什么會產(chǎn)生上面的訪問零址的錯誤。將上面的memset語句去除程序就可以正常運行了。
    所以,從上面的問題中,我們可以看出在構(gòu)造函數(shù)體內(nèi)調(diào)用memset將整個對象清0是很有風險的,當沒有虛函數(shù)的時候上面程序可以正常運行(可以試著將Base類的純虛函數(shù)聲明改成非虛函數(shù)再運行程序)。初始化類的屬性對象時,比較穩(wěn)妥的辦法還是手動逐個進行初使化。