1. 程式人生 > >C++成員函數指針錯誤用法警示(成員函數指針與高性能的C++委托,三篇),附好多評論

C++成員函數指針錯誤用法警示(成員函數指針與高性能的C++委托,三篇),附好多評論

其中 崩潰 odin 包含 版本 msvc 測試平臺 可見 是你

今天做一個成績管理系統的並發引擎,用Qt做的,仿照QtConcurrent搞了個模板基類。這裏為了隱藏細節,隔離變化,把並發的東西全部包含在模板基類中。子類只需註冊需要並發執行的入口函數即可在單獨線程中執行。最終目標是,繼承的業務邏輯類外部調用時有兩個接口可選,調用syncRun同步執行;調用由引擎自動生成的asyncRun就異步執行。最終自動生成asyncRun的模板基類沒能實現,主要原因是mingw對this處理的太有問題了!!原本以為編譯器問題,後來才知道成員函數指針和this指針如此特殊,對此篇文章反感者請移步文章末尾直接看好文。

/*!
\author LiuBao
\date 2011/3/8
\brief 匯集了各種成員函數指針的錯誤用法
*/
#include <iostream>
#include <conio.h>
 
using namespace std;
 
class B;
 
class A
{
public:
    A() {cout << "A::this->" << this << endl;}
 
    void runA()
    {
        /* 錯誤的類型轉換,拜一篇爛文所賜用上的 */
        union
        {
            void *from;             //void*類型
            void (A::*to)(int);     //A的成員函數指針類型
            void (*to2)(A*, int);   //C的函數指針類型,第一個參數是A*類型,用於傳this
        }ut;
 
        ut.from = childFuncPtr;
 
        (this->*ut.to)(0);          //錯誤調用,用父類指針,以成員函數指針方式調用子類的成員函數
 
        /* 微軟運行時庫檢測到運行時錯誤 */
        ut.to2(this, 1);            //錯誤調用,用C語言風格的函數指針,直接把this作為第一個參數傳入調用子類的成員函數
    }
 
    template<typename FromType>
    void saveFuncPtr(FromType addr)
    {
        /* 錯誤的類型轉換,拜一篇爛文所賜用上的 */
        union
        {
            FromType from;          //任意成員函數指針類型
            void *to;               //void*類型
        }ut;
 
        /* 把地址轉換為void*保存到childFuncPtr */
        ut.from = addr;
        childFuncPtr = ut.to;
    }
 
protected:
    void *childFuncPtr;             //錯誤使用,由於長度可能不同,void*不可以用來保存任意成員函數地址
};
 
class B : public A
{
public:
    B()
    {
        cout << "B::this->" << this << endl;    //打印B的this指針
        this->saveFuncPtr(&B::testThis);        //保存B::testThis地址到父類的void*成員變量
    }
 
    void runB()
    {
        /* 錯誤的類型轉換,拜一篇爛文所賜用上的 */
        union
        {
            void *from;             //void*類型
            void (B::*to)(int);     //類B的成員函數指針類型
            void (*to2)(B*, int);   //C語言的函數指針類型,第一個參數是B*類型,用於傳this
        }ut;
 
        ut.from = childFuncPtr;
 
        testThis(2);                //直接調用成員函數
        (this->*ut.to)(3);          //錯誤調用,用函數指針調用成員函數
 
        /* 微軟運行時庫檢測到運行時錯誤 */
        ut.to2(this, 4);            //錯誤調用,用C語言風格的函數指針,直接把this作為第一個參數傳入調用子類的成員函數
    }
 
    void testThis(int i) {cout << i << " -> " << this << endl;}
};
 
int main()
{
    B b;
    b.runA();
    b.runB();
 
    _getch();
 
    return 0;
}

本例旨在測試各編譯器對this的處理情況,其中有錯誤用法,請勿在實際項目中仿照使用!測試平臺Win7x64,各編譯器使用默認參數

技術分享技術分享

gcc version 4.4.0 (GCC) 左debug右release。可見用debug版中,函數指針方法調用成員函數,成員函數中的this指針是錯的!

技術分享技術分享

用於 80x86 的 Microsoft (R) 32 位 C/C++ 優化編譯器 16.00.30319.01 版左debug右release。debug版本有健全的運行時檢查,so,release時就可以放心的給出異常值了。vs處理C語言風格調用成員函數給出運行時錯誤,這一點很令人贊賞!

技術分享技術分享

這是把用C語言風格調用成員函數兩處註釋掉後vs編譯運行結果,左debug右release。this指針完全正常。Intel(R) C++ Compiler XE for applications running on IA-32, Version 12.0.0.063 Build 20100721的運行結果與vs2010幾乎完全一樣,debug版本一樣有運行時錯誤,可見是微軟的運行時庫在起作用。但是release版本直接崩潰,也許跟優化方式有關?微軟自家編譯器鏈自家庫確實有優勢,呵呵~同樣,去掉兩處C風格調用,this指針完全正常。

技術分享技術分享

Embarcadero C++ 6.31左debug右release。所有調用均輸出正確的this值!

總結:最費解的是mingw的結果(同學linux下用gcc測試結果一樣)。父類與子類有同樣的this值,同樣的函數地址,父類指針直接調用子類成員函數居然可以離譜成這樣!看來,奇技淫巧最終帶來的後果是各種不確定,不要嘗試用父類指針調用子類成員函數,更不要使用C語言的函數指針強制傳遞this指針!!

後續:現在才明白,我是試圖用模板實現自動類型推導的委托-_-! 推薦3篇該方面的好文:

成員函數指針與高性能的C++委托(上篇)

成員函數指針與高性能的C++委托(中篇)

成員函數指針與高性能的C++委托(下篇)

http://www.cnblogs.com/codingmylife/archive/2011/03/08/1976720.html

備註:在我使用的環境(VC6)裏,類的成員函數的默認調用約定是__thiscall; 關於這個調用約定,請註意在MSDN中的描述:“Arguments are pushed on the stack from right to left, with the this pointer being passed via register ECX, and not on the stack, on the x86 architecture.”
註意這段話表達的意思非常重要,在X86上,this指針是通過ECX傳遞的,而不是通過棧傳遞的。

在MSDN中還有比較重要的信息是,__thiscall 在VS2005.net之前的版本中無法顯示指定。
在IPF芯片和X64的機器上,__thiscall會被編譯器接受但是被忽略。所以我想這是你第一個測試環境沒有彈出錯誤對話框的原因。

http://blog.csdn.net/hifrog/archive/2004/07/03/33352.aspx
這篇裏“成員函數指針實現”一節提到的:
編譯器 選項 int DataPtr CodePtr Single Multi Virtual Unknown
MSVC 無 4 4 4 4 8 12 16
其中 void*屬於DataPtr,靜態成員函數指針也就是C語言的普通函數指針屬於CodePtr,Multi是多重繼承下的成員函數指針,Virtual是虛繼承下的成員函數指針。

如果我把多重繼承下的成員函數指針8字節長賦值給了void*(4字節長)就丟失了一部分數據。我沒試過,文中這麽寫的。

C++成員函數指針錯誤用法警示(成員函數指針與高性能的C++委托,三篇),附好多評論