不當(dāng)使用memset函數(shù)帶來(lái)的麻煩問(wèn)題

字號(hào):

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