??xml version="1.0" encoding="utf-8" standalone="yes"?>午夜肉伦伦影院久久精品免费看国产一区二区三区 ,久久久久99这里有精品10,欧美精品福利视频一区二区三区久久久精品 http://www.shnenglu.com/SpringSnow/category/8521.html雪化了,花开了,春天来了zh-cnWed, 29 Jul 2009 03:52:41 GMTWed, 29 Jul 2009 03:52:41 GMT60转:位运符的一些简单应?http://www.shnenglu.com/SpringSnow/articles/91499.htmlSandySandyTue, 28 Jul 2009 08:21:00 GMThttp://www.shnenglu.com/SpringSnow/articles/91499.htmlhttp://www.shnenglu.com/SpringSnow/comments/91499.htmlhttp://www.shnenglu.com/SpringSnow/articles/91499.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/91499.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/91499.html转过来了?br>
摘自Q?a href="http://www.shnenglu.com/pengkuny/archive/2007/04/21/22551.html">http://www.shnenglu.com/pengkuny/archive/2007/04/21/22551.html

位运符有:&(按位?、|(按位?、^(按位异或)、~ (按位取反)?
优先U从高到低,依次为~?amp;、^、|Q?

位运符的一些简单应?
按位与运有两种典型用法Q一是取一个位串信息的某几位,如以下代码截取x的最?位:x & 0177。二是让某变量保留某几位Q其余位|?Q如以下代码让x只保留最?位:x = x & 077?/p>

按位或运的典型用法是将一个位串信息的某几位置?。如要获得最??Q其他位与变量j的其他位相同Q可用逻辑或运?17|j

按位异或q算的典型用法是求一个位串信息的某几位信息的反。如Ʋ求整型变量j的最?位信息的反,用逻辑异或q算017^jQ就能求得j最?位的信息的反,卛_来ؓ1的位Q结果是0,原来?的位Q结果是1?交换两个|不用临时变量,假如a=3,b=4。想a和b的g换,可以用以下赋D句实玎ͼ
a=a^b;  b=b^a;     a=a^b;

取反q算常用来生成与pȝ实现无关的常数。如要将变量x最?位置?Q其余位不变Q可用代码x = x & ~077实现。以上代码与整数x?个字节还是用4个字节实现无兟?
当两个长度不同的数据q行位运时(例如long型数据与int型数?Q将两个q算分量的右端对齐进行位q算。如果短的数为正敎ͼ高位?补满Q如果短的数敎ͼ高位?补满。如果短的ؓ无符h敎ͼ则高位L?补满?/p>

位运用来对位串信息q行q算Q得C串信息结果。如以下代码能取下整型变量k的位串信息的最双?的信息位Q?(k-1)^k) & k?

对于带符L数据Q如果移位前W号位ؓ0(正数)Q则左端也是?补充Q如果移位前W号位ؓ1(负数)Q则左端?或用1补充Q取决于计算机系l。对于负数右U,U用0 补充的系lؓ“逻辑右移”Q用1补充的系lؓ“术右移”。以下代码能说明读者上机的pȝ所采用的右UL法:
     printf("%d\n\n\n", -2>>4);
若输出结果ؓ-1Q是采用术右移Q输出结果ؓ一个大整数Q则为逻辑右移?/p>

UMq算与位q算l合能实现许多与位串q算有关的复杂计。设变量的位自右臛_序~号Q自0位至15位,有关指定位的表达式是不超q?5的正整数。以下各代码分别有它们右Ҏ释所C的意义Q?br>   (1) 判断int型变量a是奇数还是偶?其是对大数的判?br>        a&1 == 0 偶数
        a&1 == 1 奇数
    (2) 取int型变量a的第k?(k=0,1,2……sizeof(int))
 a>>k&1
    (3) int型变量a的第k位清0
 a=a&~(1<<k)
    (4) int型变量a的第k位置1
 a=a|(1<<k)
    (5) int型变量@环左Uk?br> a=a<<k|a>>16-k   (设sizeof(int)=16)
    (6) int型变量a循环右移k?br> a=a>>k|a<<16-k   (设sizeof(int)=16)
    (7) 实现最低n位ؓ1Q其余位?的位串信?
 ~Q~0 << nQ?
    (8)截取变量x自p位开始的双n位的信息:
 (x >> (1+p-n)) & ~(~0 << n) 
    (9)截取old变量Wrow位,q将该位信息装配到变量new的第15-k?br> new |= ((old >> row) & 1) << (15 – k)
    (10)设s不等于全0Q代码寻找最双?的位的序号j:
 for(j = 0; ((1 << j) & s) == 0; j++) ;


有些用法q未考证

Sandy 2009-07-28 16:21 发表评论
]]>
? C++ 虚函数表解析http://www.shnenglu.com/SpringSnow/articles/89181.htmlSandySandyFri, 03 Jul 2009 09:32:00 GMThttp://www.shnenglu.com/SpringSnow/articles/89181.htmlhttp://www.shnenglu.com/SpringSnow/comments/89181.htmlhttp://www.shnenglu.com/SpringSnow/articles/89181.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/89181.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/89181.htmlC++ 虚函数表解析

 

陈皓

http://blog.csdn.net/haoel

 

 

前言
 

C++中的虚函数的作用主要是实C多态的机制。关于多态,而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子cȝ成员函数。这U技术可以让父类的指针有“多种形?#8221;Q这是一U泛型技术。所谓泛型技术,说白了就是试图用不变的代码来实现可变的法。比如:模板技术,RTTI技术,虚函数技术,要么是试囑ց到在~译时决议,要么试图做到q行时决议?/p>

 

 

关于虚函数的使用ҎQ我在这里不做过多的阐述。大家可以看看相关的C++的书c。在q篇文章中,我只想从虚函数的实现机制上面为大?一个清晰的剖析?/p>

 

当然Q相同的文章在网上也出现q一些了Q但我L觉这些文章不是很Ҏ阅读Q大D大D늚代码Q没有图片,没有详细的说明,没有比较Q没有D一反三。不利于学习和阅读,所以这是我惛_下这文章的原因。也希望大家多给我提意见?/p>

 

a归正传,让我们一赯入虚函数的世界?/p>

 

 

虚函数表
 

对C++ 了解的h都应该知道虚函数QVirtual FunctionQ是通过一张虚函数表(Virtual TableQ来实现的。简UCؓV-Table。在q个表中Q主是要一个类的虚函数的地址表,q张表解决了l承、覆盖的问题Q保证其容真实反应实际的函数。这P在有虚函数的cȝ实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子cȝ时候,q张虚函数表显得由为重要了Q它像一个地图一P指明了实际所应该调用的函数?/p>

 

q里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位|(q是Z保证取到虚函数表的有最高的性能——如果有多层l承或是多重l承的情况下Q?q意味着我们通过对象实例的地址得到q张虚函数表Q然后就可以遍历其中函数指针Qƈ调用相应的函数?/p>

 

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头{向了?没关p,下面是实际的例子,怿聪明的你一看就明白了?/p>

 

假设我们有这L一个类Q?/p>

 

class Base {

     public:

            virtual void f() { cout << "Base::f" << endl; }

            virtual void g() { cout << "Base::g" << endl; }

            virtual void h() { cout << "Base::h" << endl; }

 

};

 

按照上面的说法,我们可以通过Base的实例来得到虚函数表?下面是实际例E:

 

          typedef void(*Fun)(void);

 

            Base b;

 

            Fun pFun = NULL;

 

            cout << "虚函数表地址Q? << (int*)(&b) << endl;

            cout << "虚函数表 ?W一个函数地址Q? << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&b));

            pFun();

 

实际q行l果如下Q?Windows XP+VS2003,  Linux 2.6.22 + GCC 4.1.3)

 

虚函数表地址Q?012FED4

虚函数表 ?W一个函数地址Q?044F148

Base::f

 

 

通过q个CZQ我们可以看刎ͼ我们可以通过?amp;b转成int *Q取得虚函数表的地址Q然后,再次取址可以得到第一个虚函数的地址了,也就是Base::f()Q这在上面的E序中得C验证Q把int* 强制转成了函数指针)。通过q个CZQ我们就可以知道如果要调用Base::g()和Base::h()Q其代码如下Q?/p>

 

            (Fun)*((int*)*(int*)(&b)+0);  // Base::f()

            (Fun)*((int*)*(int*)(&b)+1);  // Base::g()

            (Fun)*((int*)*(int*)(&b)+2);  // Base::h()

 

q个时候你应该懂了吧。什么?q是有点晕。也是,q样的代码看着太ؕ了。没问题Q让我画个图解释一下。如下所C:

 

 


注意Q在上面q个图中Q我在虚函数表的最后多加了一个结点,q是虚函数表的结束结点,像字符串的l束W?#8220;\0”一P其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,q个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,q个值是如果1Q表C有下一个虚函数表,如果值是0Q表C是最后一个虚函数表?/p>

 

 

下面Q我分别说?#8220;无覆?#8221;?#8220;有覆?#8221;时的虚函数表的样子。没有覆盖父cȝ虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是ؓ了给一个对比。在比较之下Q我们可以更加清楚地知道其内部的具体实现?/p>

 

一般承(无虚函数覆盖Q?br> 

下面Q再让我们来看看l承时的虚函数表是什么样的。假设有如下所C的一个承关p:

 

 


 

h意,在这个承关pMQ子cL有重载Q何父cȝ函数。那么,在派生类的实例中Q其虚函数表如下所C:

 

 

对于实例QDerive d; 的虚函数表如下:

 

 

 

我们可以看到下面几点Q?/p>

1Q虚函数按照其声明顺序放于表中?/p>

2Q父cȝ虚函数在子类的虚函数前面?/p>

 

我相信聪明的你一定可以参考前面的那个E序Q来~写一D늨序来验证?/p>

 

 

 

一般承(有虚函数覆盖Q?br> 

覆盖父类的虚函数是很昄的事情,不然Q虚函数变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数Q会是一个什么样子?假设Q我们有下面q样的一个承关pR?/p>

 

 


 

Z让大家看到被l承q后的效果,在这个类的设计中Q我只覆盖了父类的一个函敎ͼf()。那么,对于zcȝ实例Q其虚函数表会是下面的一个样子:

 

 


 


我们从表中可以看C面几点,

1Q覆盖的f()函数被放C虚表中原来父c虚函数的位|?/p>

2Q没有被覆盖的函C旧?/p>

 

q样Q我们就可以看到对于下面q样的程序,

 

            Base *b = new Derive();

 

            b->f();

 

由b所指的内存中的虚函数表的f()的位|已l被Derive::f()函数地址所取代Q于是在实际调用发生Ӟ是Derive::f()被调用了。这实C多态?/p>

 

 

 

多重l承Q无虚函数覆盖)
 

下面Q再让我们来看看多重l承中的情况Q假设有下面q样一个类的承关pR注意:子类q没有覆盖父cȝ函数?/p>

 


 

 

对于子类实例中的虚函数表Q是下面q个样子Q?/p>

 


 


我们可以看到Q?/p>

1Q?nbsp; 每个父类都有自己的虚表?/p>

2Q?nbsp; 子类的成员函数被攑ֈ了第一个父cȝ表中。(所谓的W一个父cL按照声明序来判断的Q?/p>

 

q样做就是ؓ了解决不同的父类cd的指针指向同一个子cd例,而能够调用到实际的函数?/p>

 

 

 

 

多重l承Q有虚函数覆盖)
 

下面我们再来看看Q如果发生虚函数覆盖的情c?/p>

 

下图中,我们在子cM覆盖了父cȝf()函数?/p>

 


 

 

下面是对于子cd例中的虚函数表的图:

 

 


 

我们可以看见Q三个父c虚函数表中的f()的位|被替换成了子类的函数指针。这P我们可以Q一静态类型的父类来指向子c,q调用子cȝf()了。如Q?/p>

 

            Derive d;

            Base1 *b1 = &d;

            Base2 *b2 = &d;

            Base3 *b3 = &d;

            b1->f(); //Derive::f()

            b2->f(); //Derive::f()

            b3->f(); //Derive::f()

 

            b1->g(); //Base1::g()

            b2->g(); //Base2::g()

            b3->g(); //Base3::g()

 

 

安全?br> 

每次写C++的文章,d不了要批判一下C++。这文章也不例外。通过上面的讲qͼ怿我们对虚函数表有一个比较细致的了解了。水可蝲舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来q点什么坏事吧?/p>

 

一、通过父类型的指针讉K子类自己的虚函数

我们知道Q子cL有重载父cȝ虚函数是一件毫无意义的事情。因为多态也是要Z函数重蝲的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数Q但我们Ҏ不可能用下面的语句来调用子cȝ自有虚函敎ͼ

 

          Base1 *b1 = new Derive();

            b1->f1();  //~译出错

 

M妄图使用父类指针惌用子cM的未覆盖父类的成员函数的行ؓ都会被编译器视ؓ非法Q所以,q样的程序根本无法编译通过。但在运行时Q我们可以通过指针的方式访问虚函数表来辑ֈq反C++语义的行为。(关于q方面的试Q通过阅读后面附录的代码,怿你可以做到这一点)

 

 

二、访问non-public的虚函数

另外Q如果父cȝ虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用讉K虚函数表的方式来讉Kq些non-public的虚函数Q这是很Ҏ做到的?/p>

 

如:

 

class Base {

    private:

            virtual void f() { cout << "Base::f" << endl; }

 

};

 

class Derive : public Base{

 

};

 

typedef void(*Fun)(void);

 

void main() {

    Derive d;

    Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);

    pFun();

}

 

 

l束?br>C++q门语言是一门Magic的语aQ对于程序员来说Q我们似乎永q摸不清楚这门语a背着我们在干了什么。需要熟悉这门语aQ我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不Ӟq是一U搬L头砸自己脚的~程语言?/p>

 

在文章束之前q是介绍一下自己吧。我从事软g研发有十个年头了Q目前是软g开发技术主,技术方面,LUnix/C/C++Q比较喜Ƣ网l上的技术,比如分布式计,|格计算QP2PQAjax{一切和互联|相关的东西。管理方面比较擅长于团队Q技术趋势分析,目理。欢q大家和我交,我的MSN和Email是:haoel@hotmail.com 

 

附录一QVC中查看虚函数?br> 

我们可以在VC的IDE环境中的Debug状态下展开cȝ实例可以看到虚函数表了Qƈ不是很完整的Q?/p>

 

 

附录 二:例程
下面是一个关于多重承的虚函数表讉K的例E:

 

#include <iostream>

using namespace std;

 

class Base1 {

public:

            virtual void f() { cout << "Base1::f" << endl; }

            virtual void g() { cout << "Base1::g" << endl; }

            virtual void h() { cout << "Base1::h" << endl; }

 

};

 

class Base2 {

public:

            virtual void f() { cout << "Base2::f" << endl; }

            virtual void g() { cout << "Base2::g" << endl; }

            virtual void h() { cout << "Base2::h" << endl; }

};

 

class Base3 {

public:

            virtual void f() { cout << "Base3::f" << endl; }

            virtual void g() { cout << "Base3::g" << endl; }

            virtual void h() { cout << "Base3::h" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

            virtual void f() { cout << "Derive::f" << endl; }

            virtual void g1() { cout << "Derive::g1" << endl; }

};

 

 

typedef void(*Fun)(void);

 

int main()

{

            Fun pFun = NULL;

 

            Derive d;

            int** pVtab = (int**)&d;

 

            //Base1's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

            pFun = (Fun)pVtab[0][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

            pFun = (Fun)pVtab[0][1];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

            pFun = (Fun)pVtab[0][2];

            pFun();

 

            //Derive's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

            pFun = (Fun)pVtab[0][3];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[0][4];

            cout<<pFun<<endl;

 

 

            //Base2's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[1][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[1][1];

            pFun();

 

            pFun = (Fun)pVtab[1][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[1][3];

            cout<<pFun<<endl;

 

 

 

            //Base3's vtable

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

            pFun = (Fun)pVtab[2][0];

            pFun();

 

            //pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

            pFun = (Fun)pVtab[2][1];

            pFun();

 

            pFun = (Fun)pVtab[2][2];

            pFun();

 

            //The tail of the vtable

            pFun = (Fun)pVtab[2][3];

            cout<<pFun<<endl;

 

            return 0;

}

 

本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/haoel/archive/2007/12/18/1948051.aspx



Sandy 2009-07-03 17:32 发表评论
]]>
? C++ 对象的内存布局(?http://www.shnenglu.com/SpringSnow/articles/89178.htmlSandySandyFri, 03 Jul 2009 09:21:00 GMThttp://www.shnenglu.com/SpringSnow/articles/89178.htmlhttp://www.shnenglu.com/SpringSnow/comments/89178.htmlhttp://www.shnenglu.com/SpringSnow/articles/89178.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/89178.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/89178.htmlC++ 对象的内存布局(?

 

陈皓

http://blog.csdn.net/haoel

 

 

重复l承
 

下面我们再来看看Q发生重复承的情况。所谓重复承,也就是某个基c被间接地重复承了多次?/p>

 

下图是一个承图Q我们重载了父类的f()函数?/p>

 

 

 

其类l承的源代码如下所C。其中,每个c都有两个变量,一个是整ŞQ?字节Q,一个是字符Q?字节Q,而且q有自己的虚函数Q自己overwrite父类的虚函数。如子类D中,f()覆盖了超cȝ函数Q?f1() 和f2() 覆盖了其父类的虚函数QDf()q虚函数?/p>

 

class B

{

    public:

        int ib;

        char cb;

    public:

        B():ib(0),cb('B') {}

 

        virtual void f() { cout << "B::f()" << endl;}

        virtual void Bf() { cout << "B::Bf()" << endl;}

};

class B1 :  public B

{

    public:

        int ib1;

        char cb1;

    public:

        B1():ib1(11),cb1('1') {}

 

        virtual void f() { cout << "B1::f()" << endl;}

        virtual void f1() { cout << "B1::f1()" << endl;}

        virtual void Bf1() { cout << "B1::Bf1()" << endl;}

 

};

class B2:  public B

{

    public:

        int ib2;

        char cb2;

    public:

        B2():ib2(12),cb2('2') {}

 

        virtual void f() { cout << "B2::f()" << endl;}

        virtual void f2() { cout << "B2::f2()" << endl;}

        virtual void Bf2() { cout << "B2::Bf2()" << endl;}

       

};

 

class D : public B1, public B2

{

    public:

        int id;

        char cd;

    public:

        D():id(100),cd('D') {}

 

        virtual void f() { cout << "D::f()" << endl;}

        virtual void f1() { cout << "D::f1()" << endl;}

        virtual void f2() { cout << "D::f2()" << endl;}

        virtual void Df() { cout << "D::Df()" << endl;}

       

};

我们用来存取子类内存布局的代码如下所C:Q在VC++ 2003和G++ 3.4.4下)

    typedef void(*Fun)(void);

    int** pVtab = NULL;

    Fun pFun = NULL;

 

    D d;

    pVtab = (int**)&d;

    cout << "[0] D::B1::_vptr->" << endl;

    pFun = (Fun)pVtab[0][0];

    cout << "     [0] ";    pFun();

    pFun = (Fun)pVtab[0][1];

    cout << "     [1] ";    pFun();

    pFun = (Fun)pVtab[0][2];

    cout << "     [2] ";    pFun();

    pFun = (Fun)pVtab[0][3];

    cout << "     [3] ";    pFun();

    pFun = (Fun)pVtab[0][4];

    cout << "     [4] ";    pFun();

    pFun = (Fun)pVtab[0][5];

    cout << "     [5] 0x" << pFun << endl;

   

    cout << "[1] B::ib = " << (int)pVtab[1] << endl;

    cout << "[2] B::cb = " << (char)pVtab[2] << endl;

    cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;

    cout << "[4] B1::cb1 = " << (char)pVtab[4] << endl;

 

    cout << "[5] D::B2::_vptr->" << endl;

    pFun = (Fun)pVtab[5][0];

    cout << "     [0] ";    pFun();

    pFun = (Fun)pVtab[5][1];

    cout << "     [1] ";    pFun();

    pFun = (Fun)pVtab[5][2];

    cout << "     [2] ";    pFun();

    pFun = (Fun)pVtab[5][3];

    cout << "     [3] ";    pFun();

    pFun = (Fun)pVtab[5][4];

    cout << "     [4] 0x" << pFun << endl;

 

    cout << "[6] B::ib = " << (int)pVtab[6] << endl;

    cout << "[7] B::cb = " << (char)pVtab[7] << endl;   

    cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;

    cout << "[9] B2::cb2 = " << (char)pVtab[9] << endl;

 

    cout << "[10] D::id = " << (int)pVtab[10] << endl;

    cout << "[11] D::cd = " << (char)pVtab[11] << endl;

 

E序q行l果如下Q?/p>


 

GCC 3.4.4
 VC++ 2003
 
[0] D::B1::_vptr->

     [0] D::f()

     [1] B::Bf()

     [2] D::f1()

     [3] B1::Bf1()

     [4] D::f2()

     [5] 0x1

[1] B::ib = 0

[2] B::cb = B

[3] B1::ib1 = 11

[4] B1::cb1 = 1

[5] D::B2::_vptr->

     [0] D::f()

     [1] B::Bf()

     [2] D::f2()

     [3] B2::Bf2()

     [4] 0x0

[6] B::ib = 0

[7] B::cb = B

[8] B2::ib2 = 12

[9] B2::cb2 = 2

[10] D::id = 100

[11] D::cd = D
 [0] D::B1::_vptr->

     [0] D::f()

     [1] B::Bf()

     [2] D::f1()

     [3] B1::Bf1()

     [4] D::Df()

     [5] 0x00000000

[1] B::ib = 0

[2] B::cb = B

[3] B1::ib1 = 11

[4] B1::cb1 = 1

[5] D::B2::_vptr->

     [0] D::f()

     [1] B::Bf()

     [2] D::f2()

     [3] B2::Bf2()

     [4] 0x00000000

[6] B::ib = 0

[7] B::cb = B

[8] B2::ib2 = 12

[9] B2::cb2 = 2

[10] D::id = 100

[11] D::cd = D
 


 

下面是对于子cd例中的虚函数表的图:

 


 

 


我们可以看见Q最端的父cB其成员变量存在于B1和B2中,q被Dl承下M。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两䆾Q一份是B1l承而来的,另一份是B2l承而来的。所以,如果我们使用以下语句Q则会生二义性编译错误:

 

D d;

d.ib = 0;               //二义性错?/p>

d.B1::ib = 1;           //正确

d.B2::ib = 2;           //正确


注意Q上面例E中的最后两条语句存取的是两个变量。虽然我们消除了二义性的~译错误Q但BcdD中还是有两个实例Q这U扉K成了数据的重复Q我们叫q种l承为重复ѝ重复的基类数据成员可能q不是我们想要的。所以,C++引入了虚基类的概c?/p>

 

 

ȝ型多重虚拟?br> 

虚拟l承的出现就是ؓ了解决重复承中多个间接父类的问题的。钻矛_的结构是其最l典的结构。也是我们在q里要讨论的l构Q?/p>

 

上述?#8220;重复l承”只需要把B1和B2l承B的语法中加上virtual 关键Q就成了虚拟l承Q其l承囑֦下所C:

 

 

 

 

上图和前面的“重复l承”中的cȝ内部数据和接口都是完全一LQ只是我们采用了虚拟l承Q其省略后的源码如下所C:

 

class B {……};

class B1 : virtual public B{……};

class B2: virtual public B{……};

class D : public B1, public B2{ …… };

 

在查看D之前Q我们先看一看单一虚拟l承的情c下面是一D在VC++2003下的试E序Q(因ؓVC++和GCC的内存而局上有一些细节上的不同,所以这里只l出VC++的程序,GCC下的E序大家可以Ҏ我给出的E序自己仿照着写一个去试一试)Q?/p>

 

    int** pVtab = NULL;

    Fun pFun = NULL;

 

    B1 bb1;

 

    pVtab = (int**)&bb1;

    cout << "[0] B1::_vptr->" << endl;

    pFun = (Fun)pVtab[0][0];

    cout << "     [0] ";

    pFun(); //B1::f1();

    cout << "     [1] ";

    pFun = (Fun)pVtab[0][1];

    pFun(); //B1::bf1();

    cout << "     [2] ";

    cout << pVtab[0][2] << endl;

 

    cout << "[1] = 0x";

    cout << (int*)*((int*)(&bb1)+1) <<endl; //B1::ib1

    cout << "[2] B1::ib1 = ";

    cout << (int)*((int*)(&bb1)+2) <<endl; //B1::ib1

    cout << "[3] B1::cb1 = ";

    cout << (char)*((int*)(&bb1)+3) << endl; //B1::cb1

 

    cout << "[4] = 0x";

    cout << (int*)*((int*)(&bb1)+4) << endl; //NULL

 

    cout << "[5] B::_vptr->" << endl;

    pFun = (Fun)pVtab[5][0];

    cout << "     [0] ";

    pFun(); //B1::f();

    pFun = (Fun)pVtab[5][1];

    cout << "     [1] ";

    pFun(); //B::Bf();

    cout << "     [2] ";

    cout << "0x" << (Fun)pVtab[5][2] << endl;

 

    cout << "[6] B::ib = ";

    cout << (int)*((int*)(&bb1)+6) <<endl; //B::ib

    cout << "[7] B::cb = ";

 

其运行结果如下(我结ZGCC的和VC++2003的对比)Q?/p>

 

GCC 3.4.4
 VC++ 2003
 
[0] B1::_vptr ->

    [0] : B1::f()

    [1] : B1::f1()

    [2] : B1::Bf1()

    [3] : 0

[1] B1::ib1 : 11

[2] B1::cb1 : 1

[3] B::_vptr ->

    [0] : B1::f()

    [1] : B::Bf()

    [2] : 0

[4] B::ib : 0

[5] B::cb : B

[6] NULL : 0
 [0] B1::_vptr->

     [0] B1::f1()

     [1] B1::Bf1()

     [2] 0

[1] = 0x00454310 ç该地址取值后?4

[2] B1::ib1 = 11

[3] B1::cb1 = 1

[4] = 0x00000000

[5] B::_vptr->

     [0] B1::f()

     [1] B::Bf()

     [2] 0x00000000

[6] B::ib = 0

[7] B::cb = B
 

 

 

q里Q大家可以自己对比一下。关于细节上Q我会在后面一q再说?/p>

 

下面的测试程序是看子cD的内存布局Q同hVC++ 2003的(因ؓVC++和GCC的内存布局上有一些细节上的不同,而VC++的相对要清楚很多Q所以这里只l出VC++的程序,GCC下的E序大家可以Ҏ我给出的E序自己仿照着写一个去试一试)Q?/p>

 

    D d;

 

    pVtab = (int**)&d;

    cout << "[0] D::B1::_vptr->" << endl;

    pFun = (Fun)pVtab[0][0];

    cout << "     [0] ";    pFun(); //D::f1();

    pFun = (Fun)pVtab[0][1];

    cout << "     [1] ";    pFun(); //B1::Bf1();

    pFun = (Fun)pVtab[0][2];

    cout << "     [2] ";    pFun(); //D::Df();

    pFun = (Fun)pVtab[0][3];

    cout << "     [3] ";

    cout << pFun << endl;

 

    //cout << pVtab[4][2] << endl;

    cout << "[1] = 0x";

    cout <<  (int*)((&dd)+1) <<endl; //????

 

    cout << "[2] B1::ib1 = ";

    cout << *((int*)(&dd)+2) <<endl; //B1::ib1

    cout << "[3] B1::cb1 = ";

    cout << (char)*((int*)(&dd)+3) << endl; //B1::cb1

 

    //---------------------

    cout << "[4] D::B2::_vptr->" << endl;

    pFun = (Fun)pVtab[4][0];

    cout << "     [0] ";    pFun(); //D::f2();

    pFun = (Fun)pVtab[4][1];

    cout << "     [1] ";    pFun(); //B2::Bf2();

    pFun = (Fun)pVtab[4][2];

    cout << "     [2] ";

    cout << pFun << endl;

   

    cout << "[5] = 0x";

    cout << *((int*)(&dd)+5) << endl; // ???

 

    cout << "[6] B2::ib2 = ";

    cout << (int)*((int*)(&dd)+6) <<endl; //B2::ib2

    cout << "[7] B2::cb2 = ";

    cout << (char)*((int*)(&dd)+7) << endl; //B2::cb2

 

    cout << "[8] D::id = ";

    cout << *((int*)(&dd)+8) << endl; //D::id

    cout << "[9] D::cd = ";

    cout << (char)*((int*)(&dd)+9) << endl;//D::cd

 

    cout << "[10]  = 0x";

    cout << (int*)*((int*)(&dd)+10) << endl;

    //---------------------

    cout << "[11] D::B::_vptr->" << endl;

    pFun = (Fun)pVtab[11][0];

    cout << "     [0] ";    pFun(); //D::f();

    pFun = (Fun)pVtab[11][1];

    cout << "     [1] ";    pFun(); //B::Bf();

    pFun = (Fun)pVtab[11][2];

    cout << "     [2] ";

    cout << pFun << endl;

 

    cout << "[12] B::ib = ";

    cout << *((int*)(&dd)+12) << endl; //B::ib

    cout << "[13] B::cb = ";

    cout << (char)*((int*)(&dd)+13) <<endl;//B::cb

 

下面l出q行后的l果Q分VC++和GCC两部份)

 

 

GCC 3.4.4
 VC++ 2003
 
[0] B1::_vptr ->

    [0] : D::f()

    [1] : D::f1()

    [2] : B1::Bf1()

    [3] : D::f2()

    [4] : D::Df()

    [5] : 1

[1] B1::ib1 : 11

[2] B1::cb1 : 1

[3] B2::_vptr ->

    [0] : D::f()

    [1] : D::f2()

    [2] : B2::Bf2()

    [3] : 0

[4] B2::ib2 : 12

[5] B2::cb2 : 2

[6] D::id : 100

[7] D::cd : D

[8] B::_vptr ->

    [0] : D::f()

    [1] : B::Bf()

    [2] : 0

[9] B::ib : 0

[10] B::cb : B

[11] NULL : 0
 [0] D::B1::_vptr->

     [0] D::f1()

     [1] B1::Bf1()

     [2] D::Df()

     [3] 00000000

[1] = 0x0013FDC4  ç 该地址取值后?4

[2] B1::ib1 = 11

[3] B1::cb1 = 1

[4] D::B2::_vptr->

     [0] D::f2()

     [1] B2::Bf2()

     [2] 00000000

[5] = 0x4539260   ç 该地址取值后?4

[6] B2::ib2 = 12

[7] B2::cb2 = 2

[8] D::id = 100

[9] D::cd = D

[10]  = 0x00000000

[11] D::B::_vptr->

     [0] D::f()

     [1] B::Bf()

     [2] 00000000

[12] B::ib = 0

[13] B::cb = B
 


 

 

关于虚拟l承的运行结果我׃d了(前面的作囑ַl让我生了很严重的厌倦感Q所以就偷个懒了Q大家见谅了Q?/p>

 

在上面的输出l果中,我用不同的颜色做了一些标明。我们可以看到如下的几点Q?/p>

 

1Q无论是GCCq是VC++Q除了一些细节上的不同,其大体上的对象布局是一L。也是_先是B1Q黄ԌQ然后是B2Q绿ԌQ接着是DQ灰ԌQ而Bq个类Q青蓝色Q的实例都放在最后的位置?/p>

 

2Q关于虚函数表,其是第一个虚表,GCC和VC++有很重大的不一栗但仔细看下来,q是VC++的虚表比较清晰和有逻辑性?/p>

 

3QVC++和GCC都把Bq个类攑ֈ了最后,而VC++有一个NULL分隔W把B和B1和B2的布局分开。GCC则没有?/p>

 

4QVC++中的内存布局有两个地址我有些不是很明白Q在其中我用U色标出了。取其内Ҏ-4。接道理来说Q这个指针应该是指向Bcd例的内存地址Q这个做法就是ؓ了保证重复的父类只有一个实例的技术)。但取值后却不是。这Ҏ目前qƈ不太清楚Q还向大家请教?/p>

 

5QGCC的内存布局中在B1和B2中则没有指向B的指针。这点可以理解,~译器可以通过计算B1和B2的size而得出B的偏U量?/p>

 

 

l束?br>C++q门语言是一门比较复杂的语言Q对于程序员来说Q我们似乎永q摸不清楚这门语a背着我们在干了什么。需要熟悉这门语aQ我们就必需要了解C++里面的那些东西,需要我们去了解他后面的内存对象。这h们才能真正的了解C++Q从而能够更好的使用C++q门最隄~程语言?/p>

 

在文章束之前q是介绍一下自己吧。我从事软g研发有十个年头了Q目前是软g开发技术主,技术方面,LUnix/C/C++Q比较喜Ƣ网l上的技术,比如分布式计,|格计算QP2PQAjax{一切和互联|相关的东西。管理方面比较擅长于团队Q技术趋势分析,目理。欢q大家和我交,我的MSN和Email是:haoel@hotmail.


本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/haoel/archive/2008/10/15/3081385.aspx



Sandy 2009-07-03 17:21 发表评论
]]>
? C++ 对象的内存布局(?http://www.shnenglu.com/SpringSnow/articles/89177.htmlSandySandyFri, 03 Jul 2009 09:20:00 GMThttp://www.shnenglu.com/SpringSnow/articles/89177.htmlhttp://www.shnenglu.com/SpringSnow/comments/89177.htmlhttp://www.shnenglu.com/SpringSnow/articles/89177.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/89177.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/89177.html好文?怕找不到?先{到这?好以后满满品?

C++ 对象的内存布局(?

 

陈皓

http://blog.csdn.net/haoel

 

前言
 

07q?2月,我写了一《C++虚函数表解析》的文章Q引起了大家的兴。有很多朋友Ҏ的文章留了言Q有鼓励我的Q有批评我的Q还有很多问问题的。我在这里一q对大家的留a表示感谢。这也是我ؓ什么再写一箋a的原因。因为,在上一文章中Q我用了的示例都是非常简单的Q主要是Z说明一些机理上的问题,也是Z图一些表达上方便和简单。不惻Iq篇文章成ؓ了打开C++对象模型内存布局的一个引子,引发了大家对C++对象的更深层ơ的讨论。当Ӟ我之前的文章q有很多斚w没有涉及Q从我个人感觉下来,在谈函数表里Q至有以下q些内容没有涉及Q?/p>

 

1Q有成员变量的情c?/p>

2Q有重复l承的情c?/p>

3Q有虚拟l承的情c?/p>

4Q有ȝ型虚拟承的情况?/p>

 

q些都是我本文章需要向大家说明的东ѝ所以,q篇文章会是《C++虚函数表解析》的一个箋,也是一高U进阶的文章。我希望大家在读q篇文章之前对C++有一定的基础和了解,q能先读我的上一文章。因文章的深度可能会比较深Q而且会比较杂乱,我希望你在读本篇文章时不会有大脑思维紊ؕD大脑L的情c?-)

 

对象的媄响因?br> 

而言之,我们一个类可能会有如下的媄响因素:

 

1Q成员变?/p>

2Q虚函数Q生虚函数表)

3Q单一l承Q只l承于一个类Q?/p>

4Q多重承(l承多个c)

5Q重复承(l承的多个父cM其父cL相同的超c)

6Q虚拟承(使用virtual方式l承Qؓ了保证承后父类的内存布局只会存在一份)

上述的东襉K常是C++q门语言在语义方面对对象内部的媄响因素,当然Q还会有~译器的影响Q比如优化)Q还有字节对齐的影响。在q里我们都不讨论Q我们只讨论C++语言上的影响?/p>

 

本篇文章着重讨Zq几个情况下的C++对象的内存布局情况?/p>

 

1Q单一的一般承(带成员变量、虚函数、虚函数覆盖Q?/p>

2Q单一的虚拟承(带成员变量、虚函数、虚函数覆盖Q?/p>

3Q多重承(带成员变量、虚函数、虚函数覆盖Q?/p>

4Q重复多重承(带成员变量、虚函数、虚函数覆盖Q?/p>

5Q钻矛_的虚拟多重承(带成员变量、虚函数、虚函数覆盖Q?/p>

 

我们的目标就是,让事情越来越复杂?/p>

 

知识复习
 

我们单地复习一下,我们可以通过对象的地址来取得虚函数表的地址Q如Q?/p>

 

          typedef void(*Fun)(void);

 

            Base b;

 

            Fun pFun = NULL;

 

            cout << "虚函数表地址Q? << (int*)(&b) << endl;

            cout << "虚函数表 ?W一个函数地址Q? << (int*)*(int*)(&b) << endl;

 

            // Invoke the first virtual function 

            pFun = (Fun)*((int*)*(int*)(&b));

            pFun();

 

我们同样可以用这U方式来取得整个对象实例的内存布局。因些东西在内存中都是连l分布的Q我们只需要用适当的地址偏移量,我们可以获得整个内存对象的布局?/p>

 

本篇文章中的例程或内存布局主要使用如下~译器和pȝQ?/p>

1QWindows XP ?VC++ 2003

2QCygwin ?G++ 3.4.4

 

单一的一般?br> 

下面Q我们假设有如下所C的一个承关p:

 

 


 

h意,在这个承关pMQ父c,子类Q孙子类都有自己的一个成员变量。而了c覆盖了父类的f()ҎQ孙子类覆盖了子cȝg_child()及其类的f()?/p>

 

我们的源E序如下所C:

 

class Parent {

public:

    int iparent;

    Parent ():iparent (10) {}

    virtual void f() { cout << " Parent::f()" << endl; }

    virtual void g() { cout << " Parent::g()" << endl; }

    virtual void h() { cout << " Parent::h()" << endl; }

 

};

 

class Child : public Parent {

public:

    int ichild;

    Child():ichild(100) {}

    virtual void f() { cout << "Child::f()" << endl; }

    virtual void g_child() { cout << "Child::g_child()" << endl; }

    virtual void h_child() { cout << "Child::h_child()" << endl; }

};

 

class GrandChild : public Child{

public:

    int igrandchild;

    GrandChild():igrandchild(1000) {}

    virtual void f() { cout << "GrandChild::f()" << endl; }

    virtual void g_child() { cout << "GrandChild::g_child()" << endl; }

    virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }

};

我们使用以下E序作ؓ试E序Q(下面E序中,我用了一个int** pVtab 来作为遍历对象内存布局的指针,q样Q我可以方便地像用数l一h遍历所有的成员包括其虚函数表了Q在后面的程序中Q我也是用这LҎ的,请不必感到奇怪,Q?/p>

 

    typedef void(*Fun)(void);

 

    GrandChild gc;

   

 

    int** pVtab = (int**)&gc;

 

    cout << "[0] GrandChild::_vptr->" << endl;

    for (int i=0; (Fun)pVtab[0][i]!=NULL; i++){

                pFun = (Fun)pVtab[0][i];

                cout << "    ["<<i<<"] ";

                pFun();

    }

    cout << "[1] Parent.iparent = " << (int)pVtab[1] << endl;

    cout << "[2] Child.ichild = " << (int)pVtab[2] << endl;

    cout << "[3] GrandChild.igrandchild = " << (int)pVtab[3] << endl;

 

其运行结果如下所C:Q在VC++ 2003和G++ 3.4.4下)

 

[0] GrandChild::_vptr->

    [0] GrandChild::f()

    [1] Parent::g()

    [2] Parent::h()

    [3] GrandChild::g_child()

    [4] Child::h1()

    [5] GrandChild::h_grandchild()

[1] Parent.iparent = 10

[2] Child.ichild = 100

[3] GrandChild.igrandchild = 1000
 

 

使用囄表示如下Q?/p>


 

 

 

 

可见以下几个斚wQ?/p>

1Q虚函数表在最前面的位|?/p>

2Q成员变量根据其l承和声明顺序依ơ放在后面?/p>

3Q在单一的承中Q被overwrite的虚函数在虚函数表中得到了更新?/p>

 

 

 

 

 

多重l承
 

下面Q再让我们来看看多重l承中的情况Q假设有下面q样一个类的承关pR注意:子类只overwrite了父cȝf()函数Q而还有一个是自己的函敎ͼ我们q样做的目的是ؓ了用g1()作ؓ一个标记来标明子类的虚函数表)。而且每个cM都有一个自q成员变量Q?/p>

 


 

 

我们的类l承的源代码如下所C:父类的成员初始ؓ10Q?0Q?0Q子cȝ?00

 

class Base1 {

public:

    int ibase1;

    Base1():ibase1(10) {}

    virtual void f() { cout << "Base1::f()" << endl; }

    virtual void g() { cout << "Base1::g()" << endl; }

    virtual void h() { cout << "Base1::h()" << endl; }

 

};

 

class Base2 {

public:

    int ibase2;

    Base2():ibase2(20) {}

    virtual void f() { cout << "Base2::f()" << endl; }

    virtual void g() { cout << "Base2::g()" << endl; }

    virtual void h() { cout << "Base2::h()" << endl; }

};

 

class Base3 {

public:

    int ibase3;

    Base3():ibase3(30) {}

    virtual void f() { cout << "Base3::f()" << endl; }

    virtual void g() { cout << "Base3::g()" << endl; }

    virtual void h() { cout << "Base3::h()" << endl; }

};

 

 

class Derive : public Base1, public Base2, public Base3 {

public:

    int iderive;

    Derive():iderive(100) {}

    virtual void f() { cout << "Derive::f()" << endl; }

    virtual void g1() { cout << "Derive::g1()" << endl; }

};

 

我们通过下面的程序来查看子类实例的内存布局Q下面程序中Q注意我使用了一个s变量Q其中用Csizof(Base)来找下一个类的偏U量。(因ؓ我声明的是int成员Q所以是4个字节,所以没有对齐问题。关于内存的寚w问题Q大家可以自行试验,我在q里׃多说了)

 

             typedef void(*Fun)(void);

 

               Derive d;

 

                int** pVtab = (int**)&d;

 

                cout << "[0] Base1::_vptr->" << endl;

                pFun = (Fun)pVtab[0][0];

                cout << "     [0] ";

                pFun();

 

                pFun = (Fun)pVtab[0][1];

                cout << "     [1] ";pFun();

 

                pFun = (Fun)pVtab[0][2];

                cout << "     [2] ";pFun();

 

                pFun = (Fun)pVtab[0][3];

                cout << "     [3] "; pFun();

 

                pFun = (Fun)pVtab[0][4];

                cout << "     [4] "; cout<<pFun<<endl;

 

                cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;

 

 

                int s = sizeof(Base1)/4;

 

                cout << "[" << s << "] Base2::_vptr->"<<endl;

                pFun = (Fun)pVtab[s][0];

                cout << "     [0] "; pFun();

 

                Fun = (Fun)pVtab[s][1];

                cout << "     [1] "; pFun();

 

                pFun = (Fun)pVtab[s][2];

                cout << "     [2] "; pFun();

 

                pFun = (Fun)pVtab[s][3];

                out << "     [3] ";

                cout<<pFun<<endl;

 

                cout << "["<< s+1 <<"] Base2.ibase2 = " << (int)pVtab[s+1] << endl;

 

                s = s + sizeof(Base2)/4;

 

                cout << "[" << s << "] Base3::_vptr->"<<endl;

                pFun = (Fun)pVtab[s][0];

                cout << "     [0] "; pFun();

 

                pFun = (Fun)pVtab[s][1];

                cout << "     [1] "; pFun();

 

                pFun = (Fun)pVtab[s][2];

                cout << "     [2] "; pFun();

 

                pFun = (Fun)pVtab[s][3];

                 cout << "     [3] ";

                cout<<pFun<<endl;

 

                s++;

                cout << "["<< s <<"] Base3.ibase3 = " << (int)pVtab[s] << endl;

                s++;

                cout << "["<< s <<"] Derive.iderive = " << (int)pVtab[s] << endl;

 

其运行结果如下所C:Q在VC++ 2003和G++ 3.4.4下)

 

[0] Base1::_vptr->

     [0] Derive::f()

     [1] Base1::g()

     [2] Base1::h()

     [3] Driver::g1()

     [4] 00000000      ç 注意Q在GCC下,q里?

[1] Base1.ibase1 = 10

[2] Base2::_vptr->

     [0] Derive::f()

     [1] Base2::g()

     [2] Base2::h()

     [3] 00000000      ç 注意Q在GCC下,q里?

[3] Base2.ibase2 = 20

[4] Base3::_vptr->

     [0] Derive::f()

     [1] Base3::g()

     [2] Base3::h()

     [3] 00000000

[5] Base3.ibase3 = 30

[6] Derive.iderive = 100
 


使用囄表示是下面这个样子:

 


 

我们可以看到Q?/p>

1Q?nbsp; 每个父类都有自己的虚表?/p>

2Q?nbsp; 子类的成员函数被攑ֈ了第一个父cȝ表中?/p>

3Q?nbsp; 内存布局中,其父cd局依次按声明顺序排列?/p>

4Q?nbsp; 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做是Z解决不同的父cȝ型的指针指向同一个子cd例,而能够调用到实际的函数?/p>

 

本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/haoel/archive/2008/10/15/3081328.aspx



Sandy 2009-07-03 17:20 发表评论
]]>
?C++ 代码技巧_头文件依赖,Pimpl?/title><link>http://www.shnenglu.com/SpringSnow/articles/80111.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Thu, 16 Apr 2009 02:11:00 GMT</pubDate><guid>http://www.shnenglu.com/SpringSnow/articles/80111.html</guid><wfw:comment>http://www.shnenglu.com/SpringSnow/comments/80111.html</wfw:comment><comments>http://www.shnenglu.com/SpringSnow/articles/80111.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/SpringSnow/comments/commentRss/80111.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/SpringSnow/services/trackbacks/80111.html</trackback:ping><description><![CDATA[今天在首늜到的,感觉不错,摘过来保存一?<a href="http://www.shnenglu.com/CornerZhang/archive/2009/04/13/79004.html"><br>http://www.shnenglu.com/CornerZhang/archive/2009/04/13/79004.html</a><br>内容如下:<br><br> 头文件依赖,Pimpl法,加速编?br>   举个例子:<br>      // File: SoundSystem.h<br><br>      #include "StreamFilter.h"<br>      #include "Emitters."<br><br>      <a title=class href="http://www.shnenglu.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>      public:<br>         // ...<br>      private:<br>         StreamFilter currentFilter;<br>         EmitModeConfig modeConfig;<br>      };<br>   一目了然的是,看得出SoundSystem实现使用了StreamFilter和EmitModeConfig的定义,所?include 了他们的定义在此SoundSystem.h中,可是随着目的不断推q,class SoundSystem中依赖的使用cd会增多,它的header被引入到其它模块中,不知不觉的编译时间越来越长,改进之:<br>      // File: SoundSystem.h<br><br>      <a title=class href="http://www.shnenglu.com/CornerZhang"><font color=#8d8c8c>class</font></a> StreamFilter;<br>      <a title=class href="http://www.shnenglu.com/CornerZhang"><font color=#8d8c8c>class</font></a> EmitModeConfig;<br><br>      <a title=class href="http://www.shnenglu.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>      public:<br>         // ...<br>      private:<br>         StreamFilter* currentFilterPtr;<br>         EmitModeConfig* modeConfigPtr;<br>      };<br><br>      // File: SoundSystem.cpp<br>      #include "StreamFilter.h"<br>      #include "Emitters."<br><br>      SoundSystem::SoundSystem() {<br>         //...<br>         currentFilterPtr = new StreamFilter;<br>         modeConfigPtr = new EmitModeConfig;<br>      }<br><br>      SoundSystem::~SoundSystem() {<br>         delete currentFilterPtr;<br>         delete modeConfigPtr;<br>         //...<br>      }<br>      q么一来,把StreamFilter和EmitModeConfig?include藏到了SoundSystem的实C码中Q以后对SoundSystem的部分改动不会导致其它模块的rebuild哦,不过由此可能会牺牲一Ҏ率吧! <p>      记得Q有位微软的C++楚人物QHerb Sutterl这U技巧称为Pimpl ( Private Implemention ), 用的恰到好处Ӟ可以提高目开发速度Q同时模块的头文仉?include关系得以~解Q可以避开循环依赖Q而且可以获得一个良好的物理设计?br><br>ȝ:<br>      PimplҎ感觉很不?<br>      使用q个Ҏ的时?一定要注意的是在这个地方的变化,q个是我W二遍看的时候才注意到的.<br>     <a title=class href="http://www.shnenglu.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>      public:<br>         // ...<br>      private:<br>         <span style="COLOR: red">StreamFilter currentFilter;<br>         EmitModeConfig modeConfig;</span><br>      };<br><br>      采用PimplҎ?变ؓ<br>     <a title=class href="http://www.shnenglu.com/CornerZhang"><font color=#8d8c8c>class</font></a> SoundSystem {<br>      public:<br>         // ...<br>      private:<br><span style="COLOR: red">         StreamFilter* currentFilterPtr;<br>         EmitModeConfig* modeConfigPtr;</span><br>      };<br>      所以在.cpp文g中就有了new和delete的操?<br><br>      对于q种Ҏ有一个疑?对于那种存在包含众多cȝ情况?q种Ҏ的驾驭不是一般h能够掌握的吧.或许q种Ҏ׃太用了,不如{待一?~译.<br><br><br></p> <img src ="http://www.shnenglu.com/SpringSnow/aggbug/80111.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/SpringSnow/" target="_blank">Sandy</a> 2009-04-16 10:11 <a href="http://www.shnenglu.com/SpringSnow/articles/80111.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>?q程的互斥运?/title><link>http://www.shnenglu.com/SpringSnow/articles/70466.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Fri, 26 Dec 2008 10:46:00 GMT</pubDate><guid>http://www.shnenglu.com/SpringSnow/articles/70466.html</guid><wfw:comment>http://www.shnenglu.com/SpringSnow/comments/70466.html</wfw:comment><comments>http://www.shnenglu.com/SpringSnow/articles/70466.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/SpringSnow/comments/commentRss/70466.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/SpringSnow/services/trackbacks/70466.html</trackback:ping><description><![CDATA[<strong>q程的互斥运?br><a >http://bbs.onlycpp.net/viewthread.php?tid=540&extra=page%3D1</a><br><br></strong>  正常情况下,一个进E的q行一般是不会影响到其他正在运行的q程的。但是对于某些有Ҏ要求的如以独占方式用串行口{硬件设备的<span id="513hrdr" class=t_tag onclick=tagshow(event) href="tag.php?name=%B3%CC%D0%F2">E序</span>p求在其进E运行期间不允许其他试图使用此端口设备的E序q行的,而且此类E序通常也不允许q行同一个程序的多个实例。这引Zq程互斥的问题?br><br>  实现q程互斥的核心思想比较单:q程在启动时首先查当前系l是否已l存在有此进E的实例Q如果没有,q程成功创建ƈ讄标识实例已经存在的标记。此后再创徏q程时将会通过该标记而知晓其实例已经存在Q从而保证进E在pȝ中只能存在一个实例。具体可以采?span class=t_tag onclick=tagshow(event) href="tag.php?name=%C4%DA%B4%E6">内存</span>映射<span id="9xnrj9l" class=t_tag onclick=tagshow(event) href="tag.php?name=%CE%C4%BC%FE">文g</span>、有名事仉、有名互斥量以及全局׃n变量{多U方法来实现。下面就分别对其中具有代表性的有名互斥量和全局׃n变量q两U方法进行介l:<br> <table class=t_table style="WIDTH: 95%" cellSpacing=0 bgColor=#333333> <tbody> <tr bgColor=#ffffff> <td><font face="Fixedsys ">// 创徏互斥?br>HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07");<br>// 查错误代?br>if (GetLastError() == ERROR_ALREADY_EXISTS) {<br> // 如果已有互斥量存在则释放<span id="f9hxz7l" class=t_tag onclick=tagshow(event) href="tag.php?name=%BE%E4%B1%FA">句柄</span>q复位互斥量<br> CloseHandle(m_hMutex);<br> m_hMutex = NULL;<br> // E序退?br> return FALSE;<br>}</font></td> </tr> </tbody> </table> <br>  上面q段代码演示了有名互斥量在进E互斥中?span class=t_tag onclick=tagshow(event) href="tag.php?name=%D3%C3%B7%A8">用法</span>。代码的核心是CreateMutexQ)Ҏ名互斥量的创建。CreateMutexQ)<span id="pj77fjp" class=t_tag onclick=tagshow(event) href="tag.php?name=%BA%AF%CA%FD">函数</span>可用来创Z个有名或无名的互斥量对象Q其函数原型为:<br> <table class=t_table style="WIDTH: 95%" cellSpacing=0 bgColor=#333333> <tbody> <tr bgColor=#ffffff> <td><font face="Fixedsys ">HANDLE CreateMutex(<br> LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的<span id="hd75xxd" class=t_tag onclick=tagshow(event) href="tag.php?name=%D6%B8%D5%EB">指针</span><br> BOOL bInitialOwner, // 初始化互斥对象的所有?br> LPCTSTR lpName // 指向互斥对象名的指针<br>);</font></td> </tr> </tbody> </table> <br>  如果函数成功执行Q将q回一个互斥量对象的句柄。如果在CreateMutexQ)执行前已l存在有相同名字的互斥量Q函数将q回q个已经存在互斥量的句柄Qƈ且可以通过GetLastErrorQ)得到错误代码ERROR_ALREADY_EXIST。可见,通过寚w误代码ERROR_ALREADY_EXIST的检可以实现CreateMutexQ)对进E的互斥?br>        建立互斥体,用来同步。如果一个线E?span class=t_tag onclick=tagshow(event) href="tag.php?name=%BB%F1%C8%A1">获取</span>了互斥体Q则要获取该互斥体的W二个线E将被挂P直到W一个线E释放该互斥体?<br><br>参数 <br>lpMutexAttributes <br>指向一个SECURITY_ATTRIBUTESl构的指针,q个l构军_互斥体句柄是否被子进E?span class=t_tag onclick=tagshow(event) href="tag.php?name=%BC%CC%B3%D0">l承</span>?nbsp;    <br>bInitialOwner <br>布尔<span id="vzvptbh" class=t_tag onclick=tagshow(event) href="tag.php?name=%C0%E0%D0%CD">cd</span>Q决定互斥体的创是否ؓ拥有?<br>lpName <br>指向互斥体名?span class=t_tag onclick=tagshow(event) href="tag.php?name=%D7%D6%B7%FB">字符</span>串的指针。互斥体可以有名字?<br>互斥体的好处是可以在q程间共?br><strong>心得体会Q?br></strong><font face="Fixedsys ">    CreateMutex() </font><font face="Arial ">用于有独占要求的E序 (在其q程q行期间不允许其他用此端口讑֤的程序运行,或不允许同名E序q行)。如有同名程序运行,则通过 <strong>GetLastErrorQ)</strong>得到错误代码 <strong>ERROR_ALREADY_EXIST</strong>?br><br>刚才又执行了下得出的l果Q程序名sampQ?br>       一般情况下Q一q入调试阶段Q进E管理器中就出现了sampq程Q执行到<font face="Fixedsys "><font face="Arial ">CreateMutex时返回进E句柄,执行到if(GetLastError() == ERROR_ALREADY_EXISTS ) q行判断Ӟ跌不执行if中的内容Q所以表C没有互斥?br>       调试之前先运行debug中的samp.exe再调试:一q入调试阶段Q进E管理器中就出现了两个sampq程Q执行到CreateMutex时返回进E句柄,执行到if(GetLastError() == ERROR_ALREADY_EXISTS ) q行判断Ӟ执行if中的内容Q表C有互斥?/font></font></font> <img src ="http://www.shnenglu.com/SpringSnow/aggbug/70466.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/SpringSnow/" target="_blank">Sandy</a> 2008-12-26 18:46 <a href="http://www.shnenglu.com/SpringSnow/articles/70466.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>? c++ noteshttp://www.shnenglu.com/SpringSnow/articles/70378.htmlSandySandyFri, 26 Dec 2008 01:15:00 GMThttp://www.shnenglu.com/SpringSnow/articles/70378.htmlhttp://www.shnenglu.com/SpringSnow/comments/70378.htmlhttp://www.shnenglu.com/SpringSnow/articles/70378.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/70378.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/70378.htmlhttp://www.shnenglu.com/mymsdn/archive/2008/12/25/cpp-notes-3.html
21、vector的动态增长优于预先分配内存?/p>

使用vector的时候最好动态地d元素。它不同于C和Java或其他语a的数据类型,Z辑ֈq箋性,更有效的Ҏ是先初始化一个空vector对象Q然后再动态添加元素,而不是预先分配内存?/p>

22、vector值初始化

内置->0

有默认构?>调用默认构?/p>

无默认构造,有其他构?>E序员手动提供初始?/p>

无默认构造,也无其他构?>标准库生一个带初值的对象

23、数l下标的cd

C++中,数组下标的正类型是size_t而不是intQsize_t是一个与机器相关的unsignedcd?/p>

24、在声明指针的时候,可以用空格将W号*与其后的标识W分隔开来,string *ps与string* ps都是可以的,但后者容易生误解,如:

string* ps1,ps2;     //ps1是指针,而ps2是一个string对象

也就是说Qh们可能误把string和string*当作两个cdQ或者说string*被当作一U新cd来看待,但这是错?/u>Q?/p>

25、一个有效的指针必然是以下三U状态之一Q?/p>

  1. 保存特定的对象的地址Q?
  2. 指向某个对象后面的另一对象Q?
  3. 或者是0倹{表明它不指向Q何对象?/li>

其中int *pi=0;与int *pi;是不同的。前者是初始化指针指?地址的对象(即ؓNULLQ(pi initialized to address to no objectQ,后者却是未初始化的Qok, but dangerous, pi is uninitializedQ?/p>

~译器可以检出0值的指针Q程序可判断该指针ƈ未指向一个对象,而未初始化的指针的用标准ƈ未定义,对大多数~译器来_如果使用未初始化的指针会指针中存放的不定D为地址Q然后操U该内存地址中存攄位内容,使用未初始化的指针相当于操纵q个不确定的地址中存储的基础数据Q因此对未初始化的指针进行解引用Ӟ通常会导致程序崩溃?/p>

26、void*指针

void*指针只支持几U有限的操作Q?/p>

  1. 与另一个指针进行比较;
  2. 向函C递void*指针或从函数q回void*指针Q?
  3. l另一个void*指针赋倹{?/li>

不允怋用void*指针操纵它所指向的对象?/p>

27、指针和引用的比较(P105Q?/p>

虽然使用引用QreferenceQ和指针都可间接讉K另一个|但它们之间有两个重要区别。第一个区别在于引用L指向某个对象Q定义引用时没有初始化是错误的。第二个重要区别则是赋D为的差异Q给引用赋g改的是该引用所兌的对象的|而ƈ不是使引用与另一个对象关联。引用一l初始化Q就始终指向同一个特定对象(q就是ؓ什么引用必d定义时初始化的原因)?/p>

28、指针与typedefQP112Q?/p>

const攑֜cd前和攑֜cd后都可以表示同样的意思:

const string s1;
string const s2;

s1和s2均表C常量字W串对象?/p>

但因此就D了下面的句子可能产生误解Q?/p>

typedef string *pstring;
const pstring cstr;

Ҏ错把typedef当成文本扩展而生下面的理解Q?/p>

const string *cstr; //qƈ非上面例子的正确意思!Q错误)

应该从声明的句子看,也就是说只看const pstring cstr;Q在q里pstring是一U指针类型,const修饰的是q个cdQ因此正的理解应该是:

string *const cstr;

而const pstring cstr;其实可以表示为pstring const cstr;Q这L写法则不Ҏ产生误解。从叛_左阅ȝ意思就是:cstr是const pstringcdQ即指向string对象的const指针?/p>

29、创建动态数l(注意点见代码注释Q?/p>

const char *cp1 = "some value";
char *cp2 = "other value";
int *piArray1 = new int[10];    //内置cd没有初始?
int *piArray2 = new int[10]();    //内置cd需要加I圆括号Q对数组元素q行初始?
std::string *psArray1 = new std::string[10];    //默认构造函数初始化
std::cout<<"----------"<<std::endl
<<"*cp1\t\t:"<<*cp1<<std::endl
<<"*cp2\t\t:"<<*cp2<<std::endl
<<"*piArray1\t:"<<*piArray1<<std::endl
<<"*piArray2\t:"<<*piArray2<<std::endl
<<"*psArray1\t:"<<*psArray1<<std::endl
<<"----------"<<std::endl;

但是下面的结果却与概念上的不同:

////Visual Studio & MS VC++
//----------
//*cp1            :s
//*cp2            :o
//*piArray1       :-842150451
//*piArray2       :0
//*psArray1       :
//----------
////Eclipse&G++
//----------
//*cp1        :s
//*cp2        :o
//*piArray1    :4064608
//*piArray2    :4064560
//*psArray1    :
//----------

看来不同的编译器Ҏ的定义还是有所不同Q注意看*piArray2的|按照说明应该是初始化?Q但q里却仍然表现出?piArray1一L|说明q没有发生初始化?/p>

对于动态分配的数组Q其元素只能初始化ؓ元素cd的默认|而不能像数组变量一P用初始化列表为数l元素提供各不相同的初倹{?/p>

30、const对象的动态数l?/p>

//P118
//error:uninitialized const array
const int *pciArray1 = new const int[10];
//ok:value-initialized const array
const int *pciArray2 = new const int[10]();
std::cout<<*pciArray1<<std::endl;
std::cout<<*pciArray2<<std::endl;

上面的示例的注释来自书中Q但在VC++~译器和G++~译器下却不同,具体表现为:

  • VC++Q编译正,W一句输出随机地址的|W二句输出初始化?Q其中按?#8220;标准”W一U因为未向const变量初始化,应该无法通过~译Q但q里可以Q?
  • G++Q编译错误,W一句的错误信息?#8220;uninitialized const in `new' of `const int'”Q但W二句按照标准应该输?的,q里却输Z随机地址的倹{?/li>

看来两个~译器对q一问题的看法不太一致?/p>

Sandy 2008-12-26 09:15 发表评论
]]>
?使用__FILE__和__LINE__定位错误 http://www.shnenglu.com/SpringSnow/articles/70318.htmlSandySandyThu, 25 Dec 2008 05:04:00 GMThttp://www.shnenglu.com/SpringSnow/articles/70318.htmlhttp://www.shnenglu.com/SpringSnow/comments/70318.htmlhttp://www.shnenglu.com/SpringSnow/articles/70318.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/70318.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/70318.html使用__FILE__和__LINE__定位错误
转自:http://www.shnenglu.com/heath/archive/2008/12/25/58046.html#70307 [前言Q用__FILE__和__LINE__来定位错误已l屡见不鲜,然而其中一些道理又有几个h仔细探究q。本文参考了Curtis Krauskopf的一名?a target=_blank>Using __FILE__ and __LINE__ to Report Errors的文章,希望辑ֈ解惑之效。]

问题Q当q行旉误生时Q我怎样才能得到包含C++文g名和行号的字W串信息Q?br>回答Q在C++中的__FILE__预编译指C器包含了被~译的文件名Q而__LINE__则包含了源代码的行号。__FILE__和__LINE__的前后都包含?span style="COLOR: red">两个下划U,让我们仔l看看__FILE__所包含的每个字W:

_ _ F I L E _ _

下面展示了在控制台程序中如果昄文g名和代码行号?br>

#include <stdio.h>

int main(int , char**)
{
     printf(
"This fake error is in %s on line %d\n",         __FILE__, __LINE__);
     
return 0;
}


输出l果Q?br>
This fake error is in c:\temp\test.cpp on line 5

让我们更上一层楼

我想通过一个通用函数error()来报告错误,以当某个错误发生时我能讄断点以及隔离错误处理Q例如,在屏q上打印错误信息或者写入日志)。因此,函数的原型应该是q样的吧Q?br>

void error(const char *file, const unsigned long line, const char *msg); 

调用方式如下Q?br>
error(__FILE__, __LINE__, "my error message");

预处理魔?br>

q里有三个问题需要解冻I

  1. __FILE__和__LINE__在每ơ调用error时作为参C入?/span>
  2. __FILE和__LINE__前后的下划线很容易被遗忘Q从而导致编译错误?/span>
  3. __LINE__是一个整敎ͼq无疑增加了error函数的复杂度。我l不想直接用整型的__LINE__Q而通常都是{换ؓ字符串打印到屏幕或写入日志文件?br>

__FILE__和__LINE__应该被自动处理,而非每次作ؓ参数传递给errorQ这样会让error的用者感觉更爽些Q它的Ş式可能是下面q样Q?br>

error(AT, "my error message");

 
我希望上面的宏AT展开为:"c:\temp\test.cpp:5"。而新的error函数则变成:

void error(const char *location, const char *msg);

 

因ؓBorland C++ Builder~译器能够自动合q相ȝ字符Ԍ因此我把AT写成下面q样Q?br>

#define AT __FILE__ ":" __LINE__ 


然而它却Ş工了Q因为__LINE__被扩展成了整数而非字符Ԍ所以宏展开后变成:

"c:\temp\test.cpp" ":"
5

q是一个无效的字符Ԍ因ؓ末尾有一个未被双引号包含的整数?

怎么办?别着急,一个特D的预编译指C器“#”能够帮我们将一个变量{换成字符丌Ӏ所以重新定义宏Q?

#define AT __FILE__ ":" #__LINE__
嘿嘿Q这h行了吧。别高兴得太早,q样也是不行的。因为编译器会抱?是个无效字符。其实,问题?预编译指C器只有q样使用才会
被正识别:
#define symbol(X) #X 

因此Q我把代码改为:

#define STRINGIFY(x) #x
#define AT __FILE__ ":" STRINGIFY(__LINE__) 

然而,奇怪的l果产生了,__LINE__居然被作Z输出的一部分Q?br>
c:\temp\test.cpp:__LINE__: fake error

解决Ҏ是再用一个宏来包装STRINGIFY()Q?br>

#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)

OKQ我们用下面的代码来试试Q?br>
#include <stdio.h>
#define STRINGIFY(x) #x
#define TOSTRING(x) STRINGIFY(x)
#define AT __FILE__ ":" TOSTRING(__LINE__)
void error(const char *location, const char *msg)
{
  printf(
"Error at %s: %s\n", location, msg);
}
int main(int , char**)
{
  error(AT, 
"fake error");
  
return 0;
}


输出l果Q?br>
Error at c:\temp\test\test.cpp:11: fake error

Visual Studio下的实践
在《Windows核心~程》中QJeffrey Richter提供了下面的宏在~译期输出有用信息:

#define chSTR2(x) #x
#define chSTR(x)  chSTR2(x)
#define chMSG(desc) message(__FILE__ "(" chSTR(__LINE__) "):" #desc)

message是一个预~译指oQ上面宏的用方法是Q?br>
#pragma chMSG(Fix this later)

l论
  1. 预编译指C器__FILE__和__LINE__能够提供有用的信息?/span>
  2. __LINE__的处理方式远比我们想象的要复杂?/span>
  3. __FILE__被处理成字符Ԍl我们带来了不少方便?


Sandy 2008-12-25 13:04 发表评论
]]>
? fopen打开文g方式 http://www.shnenglu.com/SpringSnow/articles/70317.htmlSandySandyThu, 25 Dec 2008 04:59:00 GMThttp://www.shnenglu.com/SpringSnow/articles/70317.htmlhttp://www.shnenglu.com/SpringSnow/comments/70317.htmlhttp://www.shnenglu.com/SpringSnow/articles/70317.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/70317.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/70317.htmlfopen打开文g方式

http://www.shnenglu.com/iuranus/archive/2008/12/25/70315.html

     最q写一个文件操作类Qfopen的参数着实让我搞了半天,因ؓ以前是固定的方式读写文件的Q现在要做灵zMQ所以就有些参数理解不够准确。以下是关于mode参数的定义?br>
'r' 只读方式打开Q将文g指针指向文g_如果文g不存在,则Fileq回I?br>'r+' d方式打开Q将文g指针指向文g_如果文g不存在,则Fileq回I?
'w' 写入方式打开Q将文g指针指向文g头ƈ文件大截为零。如果文件不存在则尝试创Z?
'w+' d方式打开Q将文g指针指向文g头ƈ文件大截为零。如果文件不存在则尝试创Z?
'a' 写入方式打开Q将文g指针指向文g末尾。如果文件不存在则尝试创Z?
'a+' d方式打开Q将文g指针指向文g末尾。如果文件不存在则尝试创Z?
'x' 创徏q以写入方式打开Q将文g指针指向文g头。如果文件已存在Q则 fopen() 调用p|q返?FALSE?
'x' 创徏q以写入方式打开Q将文g指针指向文g头。如果文件已存在Q则 fopen() 调用p|q返?FALSE?br>'b' 使用字符b作ؓ文gcd的判断,是否是binary文g?br>
q有在读文g时最好先判断下该文g是否存在
bool ClassA::IsFileExisted(const char* filePath)
{
   struct stat info;
   if(stat(filePath, &info) != 0)
   {
      return false;
   }
   else
      return true;
}


Sandy 2008-12-25 12:59 发表评论
]]>
C++cd转换W的使用 http://www.shnenglu.com/SpringSnow/articles/67376.htmlSandySandyThu, 20 Nov 2008 05:30:00 GMThttp://www.shnenglu.com/SpringSnow/articles/67376.htmlhttp://www.shnenglu.com/SpringSnow/comments/67376.htmlhttp://www.shnenglu.com/SpringSnow/articles/67376.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/67376.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/67376.htmlC++的四个类型{换运符已经有很久了,但一直没有弄清楚它们的用?今天看到一本书上的解释,才大致地的了解了其具体的用法.

具体归纳如下:

reinterpret_cast

该函数将一个类型的指针转换为另一个类型的指针.
q种转换不用修改指针变量值存放格?不改变指针变量?,只需在编译时重新解释指针的类型就可做?
reinterpret_cast 可以指针D{换ؓ一个整型数,但不能用于非指针cd的{?
?
//基本cd指针的类型{?br>double d=9.2;
double* pd = &d;
int *pi = reinterpret_cast<int*>(pd);  //相当于int *pi = (int*)pd;

//不相关的cȝ指针的类型{?br>class A{};
class B{};
A* pa = new A;
B* pb = reinterpret_cast<B*>(pa);   //相当于B* pb = (B*)pa;

//指针转换为整?br>long l = reinterpret_cast<long>(pi);   //相当于long l = (long)pi;


const_cast

该函数用于去除指针变量的帔R属性,它转换Z个对应指针类型的普通变量。反q来Q也可以一个非帔R的指针变量{换ؓ一个常指针变量?br>q种转换是在~译期间做出的类型更攏V?br>例:
const int* pci = 0;
int* pk = const_cast<int*>(pci);  //相当于int* pk = (int*)pci;

const A* pca = new A;
A* pa = const_cast<A*>(pca);     //相当于A* pa = (A*)pca;

Z安全性考虑Qconst_cast无法非指针的常量{换ؓ普通变量?/p>


static_cast

该函C要用于基本类型之间和hl承关系的类型之间的转换?br>q种转换一般会更改变量的内部表C方式,因此Qstatic_cast应用于指针类型{换没有太大意义?br>例:
//基本cd转换
int i=0;
double d = static_cast<double>(i);  //相当?double d = (double)i;

//转换l承cȝ对象为基cd?br>class Base{};
class Derived : public Base{};
Derived d;
Base b = static_cast<Base>(d);     //相当?Base b = (Base)d;


dynamic_cast

它与static_cast相对Q是动态{换?br>q种转换是在q行时进行{换分析的Qƈ非在~译时进行,明显区别于上面三个类型{换操作?br>该函数只能在l承cd象的指针之间或引用之间进行类型{换。进行{换时Q会Ҏ当前q行时类型信息,判断cd对象之间的{换是否合法。dynamic_cast的指针{换失败,可通过是否为null,引用转换p|则抛Z个bad_cast异常?br>例:
class Base{};
class Derived : public Base{};

//zcL针{换ؓ基类指针
Derived *pd = new Derived;
Base *pb = dynamic_cast<Base*>(pd);

if (!pb)
 cout << "cd转换p|" << endl;

//没有l承关系Q但被{换类有虚函数
class A(virtual ~A();)   //有虚函数
class B{}:
A* pa = new A;
B* pb  = dynamic_cast<B*>(pa);

如果Ҏl承关系或者没有虚函数的对象指针进行{换、基本类型指针{换以及基cL针{换ؓzcL针,都不能通过~译?/p>

Sandy 2008-11-20 13:30 发表评论
]]>
转:使应用程序只能运行一个实? http://www.shnenglu.com/SpringSnow/articles/65791.htmlSandySandySun, 02 Nov 2008 14:37:00 GMThttp://www.shnenglu.com/SpringSnow/articles/65791.htmlhttp://www.shnenglu.com/SpringSnow/comments/65791.htmlhttp://www.shnenglu.com/SpringSnow/articles/65791.html#Feedback3http://www.shnenglu.com/SpringSnow/comments/commentRss/65791.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65791.htmlhttp://topic.csdn.net/t/20020917/11/1030014.html
使应用程序只能运行一个实? 
     
        Windows是多q程操作pȝQ框架生成的应用E序可以多次q行QŞ成多个运行实例? 
  但在有些情况下ؓ保证应用E序的安全运行,要求E序只能q行一个实例,比如E?   
  序要使用只能被一个进E单独用的ҎgQ例如调制解调器Q时Q必限制程    
  序只q行一个实例? 
   
  q里涉及两个基本的问题,一是在E序的第二个实例启动Ӟ如何发现该程序已?   
  一个实例在q行Q而是如何第一个实例激z,而第二个实例退出? 
   
  对于W一个问题,可以通过l应用程序设|信号量Q实例启动时首先该信号量,    
  如已存在Q则说明E序已运行一个实例? 
   
  W二个问题的隄是获取第一个实例的ȝ对象指针或句柄,然后便可?   
  SetForegroundWindow来激zR虽然FindWindow函数能寻找正q行着的窗口,但该?   
  数要求指明所LH口的标题或H口cdQ不是实现通用Ҏ的途径? 
  我们可以用Win32   SDK函数SetProp来给应用E序ȝ讄一个特有的标记? 
  用GetDesktopWindow   可以获取WindowspȝLH口对象指针或句柄,所有应用程  
  序主H都可看成该H口的子H口Q即可用GetWindow函数来获得它们的对象指针或句  
  柄。用Win32   SDK函数GetProp查找每一应用E序ȝ是否包含有我们设|的特定? 
  C可确定它是否我们要寻扄W一个实例主H。ɽW二个实例退出很单,只要  
  让其应用E序对象的InitInstance函数q回FALSE卛_。此外,当主H口退出时Q应  
  用RemoveProp函数删除我们为其讄的标记? 
   
  下面的InitInstance、OnCreate和OnDestroy函数代码实Cq的操作Q? 
   
  BOOL   CEllipseWndApp::InitInstance()    
  {    
  //   用应用程序名创徏信号?   
  HANDLE   hSem   =   CreateSemaphore(NULL,   1,   1,   m_pszExeName);    
   
  //   信号量已存在Q?   
  //   信号量存在,则程序已有一个实例运?   
  if   (GetLastError()   ==   ERROR_ALREADY_EXISTS)    
  {    
  //   关闭信号量句?   
  CloseHandle(hSem);    
  //   L先前实例的主H口    
  HWND   hWndPrevious   =   ::GetWindow(::GetDesktopWindow(),GW_CHILD);    
  while   (::IsWindow(hWndPrevious))    
  {    
  //   查窗口是否有预设的标?    
  //   有,则是我们L的主H?   
  if   (::GetProp(hWndPrevious,   m_pszExeName))    
  {    
  //   ȝ口已最化Q则恢复其大?   
  if   (::IsIconic(hWndPrevious))    
  ::ShowWindow(hWndPrevious,SW_RESTORE);    
   
  //   主H激z?   
  ::SetForegroundWindow(hWndPrevious);    
   
  //   主H的对话框激z?   
  ::SetForegroundWindow(    
  ::GetLastActivePopup(hWndPrevious));    
   
  //   退出本实例    
  return   FALSE;    
  }    
  //   l箋L下一个窗?   
  hWndPrevious   =   ::GetWindow(hWndPrevious,GW_HWNDNEXT);  
  }    
  //   前一实例已存在,但找不到其主H?   
  //   可能出错?   
  //   退出本实例    
  return   FALSE;    
  }    
  AfxEnableControlContainer();    
  //   Standard   initialization    
  //   If   you   are   not   using   these   features   and   wish   to   reduce   the   size    
  //   of   your   final   executable,   you   should   remove   from   the   following    
  //   the   specific   initialization   routines   you   do   not   need.    
  #ifdef   _AFXDLL    
  Enable3dControls();   //   Call   this   when   using   MFC   in   a   shared   DLL    
  #else    
  Enable3dControlsStatic();//   Call   this   when   linking   to   MFC   statically    
  #endif    
   
  CEllipseWndDlg   dlg;    
  m_pMainWnd   =   &dlg;    
  int   nResponse   =   dlg.DoModal();    
  if   (nResponse   ==   IDOK)    
  {    
  //   TODO:   Place   code   here   to   handle   when   the   dialog   is    
  //   dismissed   with   OK    
  }    
  else   if   (nResponse   ==   IDCANCEL)    
  {    
  //   TODO:   Place   code   here   to   handle   when   the   dialog   is    
  //   dismissed   with   Cancel    
  }    
  //   Since   the   dialog   has   been   closed,   return   FALSE   so   that   we   exit   the    
  //   application,   rather   than   start   the   application's   message   pump.    
  return   FALSE;    
  }    
   
  int   CEllipseWndDlg::OnCreate(LPCREATESTRUCT   lpCreateStruct)      
  {    
  if   (CDialog::OnCreate(lpCreateStruct)   ==   -1)    
  return   -1;    
  //   讄L标记    
  ::SetProp(m_hWnd,   AfxGetApp()->m_pszExeName,   (HANDLE)1);    
  return   0;    
  }    
   
  void   CEllipseWndDlg::OnDestroy()      
  {    
  CDialog::OnDestroy();    
  //   删除L标记    
  ::RemoveProp(m_hWnd,   AfxGetApp()->m_pszExeName);      
  }  
           
   
  对以上代码的补充Q?   
                  查看代码和VC的帮助后Q发现问题在于原文在创徏信号量和讄L标记时? 
  的是   CWinApp   的成员变?  m_pszExeNameQ该成员变量其实是应用程序执行文件的? 
  U去掉扩展名后的部分Q而不是应用程序名?   
   
  真正的应用程序名应ؓ成员变量   m_pszAppName,    
  于是用到m_pszExeName的三处代码均改ؓm_pszAppNameQ重新编译执行,情况消失?   
   
  最后再提供一个方法和一个信息:    
   
          另一U应用E序只能q行一个实例的ҎQ只需在InitInstance()的最开始添  
  加下列语句即可:    
  HANDLE   m_hMutex=CreateMutex(NULL,TRUE,   m_pszAppName);    
  if(GetLastError()==ERROR_ALREADY_EXISTS)   {   return   FALSE;   }  
   
  但这U方法的不之处是不能将已经启动的实例激zR?   
   
                在stingray公司整理开发的MFCFAQ软g中也提供了一些方法。该软g实际是一? 
  MFC使用技巧的大汇集,对用MFC极有帮助Q各位朋友不妨去stingray公司的主下    
  载?nbsp;    
   

Sandy 2008-11-02 22:37 发表评论
]]>
转:深入出Win32多线E设计之MFC的多U程(作者:宋宝?http://www.shnenglu.com/SpringSnow/articles/65790.htmlSandySandySun, 02 Nov 2008 14:36:00 GMThttp://www.shnenglu.com/SpringSnow/articles/65790.htmlhttp://www.shnenglu.com/SpringSnow/comments/65790.htmlhttp://www.shnenglu.com/SpringSnow/articles/65790.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/65790.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65790.html转自Q?a >http://blog.ednchina.com/hotpower/174823/message.aspx 

标签Q?无标{?/p>

深入出Win32多线E设计之MFC的多U程(作者:宋宝?

1、创建和l止U程

  在MFCE序中创Z个线E,宜调用AfxBeginThread函数。该函数因参C同而具有两U重载版本,分别对应工作者线E和用户接口QUIQ线E?br>
  工作者线E?br>
CWinThread *AfxBeginThread(
 AFX_THREADPROC pfnThreadProc, //控制函数
 LPVOID pParam, //传递给控制函数的参?br> int nPriority = THREAD_PRIORITY_NORMAL, //U程的优先
 UINT nStackSize = 0, //U程的堆栈大?br> DWORD dwCreateFlags = 0, //U程的创建标?br> LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL //U程?a class=bluekey target=_blank>安全属?br>);

  工作者线E编E较为简单,只需~写U程控制函数和启动线E即可。下面的代码l出了定义一个控制函数和启动它的q程Q?br>
//U程控制函数
UINT MfcThreadProc(LPVOID lpParam)
{
 CExampleClass *lpObject = (CExampleClass*)lpParam;
 if (lpObject == NULL || !lpObject->IsKindof(RUNTIME_CLASS(CExampleClass)))
  return - 1; //输入参数非法
 //U程成功启动
 while (1)
 
 return 0;
}

//在MFCE序中启动线E?br>AfxBeginThread(MfcThreadProc, lpObject);

  UIU程

  创徏用户界面U程Ӟ必须首先从CWinThread zc,q?DECLARE_DYNCREATE ?IMPLEMENT_DYNCREATE 宏声明此cR?br>
  下面l出了CWinThreadcȝ原型Q添加了关于光要函数功能和是否需要被l承c重载的注释Q:

class CWinThread : public CCmdTarget
{
 DECLARE_DYNAMIC(CWinThread)

 public:
  // Constructors
  CWinThread();
  BOOL CreateThread(DWORD dwCreateFlags = 0, UINT nStackSize = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

  // Attributes
  CWnd* m_pMainWnd; // main window (usually same AfxGetApp()->m_pMainWnd)
  CWnd* m_pActiveWnd; // active main window (may not be m_pMainWnd)
  BOOL m_bAutoDelete; // enables 'delete this' after thread termination

  // only valid while running
  HANDLE m_hThread; // this thread's HANDLE
  operator HANDLE() const;
  DWORD m_nThreadID; // this thread's ID

  int GetThreadPriority();
  BOOL SetThreadPriority(int nPriority);

  // Operations
  DWORD SuspendThread();
  DWORD ResumeThread();
  BOOL PostThreadMessage(UINT message, WPARAM wParam, LPARAM lParam);

  // Overridables
  //执行U程实例初始化,必须重写
  virtual BOOL InitInstance();

  // running and idle processing
  //控制U程的函敎ͼ包含消息泵,一般不重写
  virtual int Run();

  //消息调度到TranslateMessage和DispatchMessage之前对其q行{选,
  //通常不重?br>  virtual BOOL PreTranslateMessage(MSG* pMsg);

  virtual BOOL PumpMessage(); // low level message pump

  //执行U程特定的闲|时间处理,通常不重?br>  virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing
  virtual BOOL IsIdleMessage(MSG* pMsg); // checks for special messages

  //U程l止时执行清除,通常需要重?br>  virtual int ExitInstance(); // default will 'delete this'

  //截获qE的消息和命令处理程序引发的未处理异常,通常不重?br>  virtual LRESULT ProcessWndProcException(CException* e, const MSG* pMsg);

  // Advanced: handling messages sent to message filter hook
  virtual BOOL ProcessMessageFilter(int code, LPMSG lpMsg);

  // Advanced: virtual access to m_pMainWnd
  virtual CWnd* GetMainWnd();

  // Implementation
 public:
  virtual ~CWinThread();
  #ifdef _DEBUG
   virtual void AssertValid() const;
   virtual void Dump(CDumpContext& dc) const;
   int m_nDisablePumpCount; // Diagnostic trap to detect illegal re-entrancy
  #endif
  void CommonConstruct();
  virtual void Delete();
  // 'delete this' only if m_bAutoDelete == TRUE

  // message pump for Run
  MSG m_msgCur; // current message

 public:
  // constructor used by implementation of AfxBeginThread
  CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam);

  // valid after construction
  LPVOID m_pThreadParams; // generic parameters passed to starting function
  AFX_THREADPROC m_pfnThreadProc;

  // set after OLE is initialized
  void (AFXAPI* m_lpfnOleTermOrFreeLib)(BOOL, BOOL);
  COleMessageFilter* m_pMessageFilter;

 protected:
  CPoint m_ptCursorLast; // last mouse position
  UINT m_nMsgLast; // last mouse message
  BOOL DispatchThreadMessageEx(MSG* msg); // helper
  void DispatchThreadMessage(MSG* msg); // obsolete
};

  启动UIU程的AfxBeginThread函数的原型ؓQ?br>
CWinThread *AfxBeginThread(
 //从CWinThreadz的类?RUNTIME_CLASS
 CRuntimeClass *pThreadClass,
 int nPriority = THREAD_PRIORITY_NORMAL,
 UINT nStackSize = 0,
 DWORD dwCreateFlags = 0,
 LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);

  我们可以方便C用VC++ 6.0cd导定义一个承自CWinThread的用LE类。下面给Z生我们自定义的CWinThread子类CMyUIThread的方法?br>
  打开VC++ 6.0cd|在如下窗口中选择Base ClasscMؓCWinThreadQ输入子cd为CMyUIThreadQ点?OK"按钮后就产生了类CMyUIThread?br>

  其源代码框架为:

/////////////////////////////////////////////////////////////////////////////
// CMyUIThread thread

class CMyUIThread : public CWinThread
{
 DECLARE_DYNCREATE(CMyUIThread)
 protected:
  CMyUIThread(); // protected constructor used by dynamic creation

  // Attributes
 public:

  // Operations
 public:

  // Overrides
  // ClassWizard generated virtual function overrides
  //{{AFX_VIRTUAL(CMyUIThread)
  public:
   virtual BOOL InitInstance();
   virtual int ExitInstance();
  //}}AFX_VIRTUAL

  // Implementation
 protected:
  virtual ~CMyUIThread();

  // Generated message map functions
  //{{AFX_MSG(CMyUIThread)
   // NOTE - the ClassWizard will add and remove member functions here.
  //}}AFX_MSG

 DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////
// CMyUIThread

IMPLEMENT_DYNCREATE(CMyUIThread, CWinThread)

CMyUIThread::CMyUIThread()
{}

CMyUIThread::~CMyUIThread()
{}

BOOL CMyUIThread::InitInstance()
{
 // TODO: perform and per-thread initialization here
 return TRUE;
}

int CMyUIThread::ExitInstance()
{
 // TODO: perform any per-thread cleanup here
 return CWinThread::ExitInstance();
}

BEGIN_MESSAGE_MAP(CMyUIThread, CWinThread)
//{{AFX_MSG_MAP(CMyUIThread)
// NOTE - the ClassWizard will add and remove mapping macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

  使用下列代码可以启动这个UIU程Q?br>
CMyUIThread *pThread;
pThread = (CMyUIThread*)
AfxBeginThread( RUNTIME_CLASS(CMyUIThread) );

  另外Q我们也可以不用AfxBeginThread 创徏U程Q而是分如下两步完成:

  Q?Q调用线E类的构造函数创Z个线E对象;

  Q?Q调用CWinThread::CreateThread函数来启动该U程?br>
  在线E自w内调用AfxEndThread函数可以l止该线E:

void AfxEndThread(
 UINT nExitCode //the exit code of the thread
);


  对于UIU程而言Q如果消息队列中攑օ了WM_QUIT消息Q将l束U程?br>
  关于UIU程和工作者线E的分配Q最好的做法是:所有与UI相关的操作放入主U程Q其它的Ua的运工作交l独立的C工作者线E?br>
  候捷先生早些旉喜欢为MDIE序的每个窗口创Z个线E,他后来澄清了q个错误。因为如果ؓMDIE序的每个窗口都单独创徏一个线E,在窗口进行切换的时候,进行线E的上下文切换!

2.U程间通信

  MFC中定义了l承自CSyncObjectcȝCCriticalSection 、CCEvent、CMutex、CSemaphorecd装和化了WIN32 API所提供的界区、事件、互斥和信号量。用这些同步机Ӟ必须包含"Afxmt.h"头文件。下囄Zcȝl承关系Q?br>

  作ؓCSyncObjectcȝl承c,我们仅仅使用基类CSyncObject的接口函数就可以方便、统一的操作CCriticalSection 、CCEvent、CMutex、CSemaphorec,下面是CSyncObjectcȝ原型Q?br>
class CSyncObject : public CObject
{
 DECLARE_DYNAMIC(CSyncObject)

 // Constructor
 public:
  CSyncObject(LPCTSTR pstrName);

  // Attributes
 public:
  operator HANDLE() const;
  HANDLE m_hObject;

  // Operations
  virtual BOOL Lock(DWORD dwTimeout = INFINITE);
  virtual BOOL Unlock() = 0;
  virtual BOOL Unlock(LONG /* lCount */, LPLONG /* lpPrevCount="NULL" */)
  { return TRUE; }

  // Implementation
 public:
  virtual ~CSyncObject();
  #ifdef _DEBUG
   CString m_strName;
   virtual void AssertValid() const;
   virtual void Dump(CDumpContext& dc) const;
  #endif
  friend class CSingleLock;
  friend class CMultiLock;
};

  CSyncObjectcL主要的两个函数是Lock和UnlockQ若我们直接使用CSyncObjectcd其派生类Q我们需要非常小心地在Lock之后调用Unlock?br>
  MFC提供的另两个cCSingleLockQ等待一个对象)和CMultiLockQ等待多个对象)为我们编写应用程序提供了更灵zȝ机制Q下面以实际来阐qCSingleLock的用法:

class CThreadSafeWnd

  ~CThreadSafeWnd(){}
  void SetWindow(CWnd *pwnd)
  {
   m_pCWnd = pwnd;
  }
  void PaintBall(COLORREF color, CRect &rc);
 private:
  CWnd *m_pCWnd;
  CCriticalSection m_CSect;
};

void CThreadSafeWnd::PaintBall(COLORREF color, CRect &rc)
{
 CSingleLock csl(&m_CSect);
 //~省的Timeout是INFINITEQ只有m_Csect被激z,csl.Lock()才能q回
 //trueQ这里一直等?br> if (csl.Lock())
;
 {
  // not necessary
  //AFX_MANAGE_STATE(AfxGetStaticModuleState( ));
  CDC *pdc = m_pCWnd->GetDC();
  CBrush brush(color);
  CBrush *oldbrush = pdc->SelectObject(&brush);
  pdc->Ellipse(rc);
  pdc->SelectObject(oldbrush);
  GdiFlush(); // don't wait to update the display
 }
}

  上述实例讲述了用CSingleLock对Windows GDI相关对象q行保护的方法,下面再给Z个其他方面的例子Q?br>
int array1[10], array2[10];
CMutexSection section; //创徏一个CMutexcȝ对象

//赋值线E控制函?br>UINT EvaluateThread(LPVOID param)
{
 CSingleLock singlelock;
 singlelock(&section);

 //互斥区域
 singlelock.Lock();
 for (int i = 0; i < 10; i++)
  array1[i] = i;
 singlelock.Unlock();
}
//拯U程控制函数
UINT CopyThread(LPVOID param)
{
 CSingleLock singlelock;
 singlelock(&section);

 //互斥区域
 singlelock.Lock();
 for (int i = 0; i < 10; i++)
  array2[i] = array1[i];
 singlelock.Unlock();
}
}

AfxBeginThread(EvaluateThread, NULL); //启动赋值线E?br>AfxBeginThread(CopyThread, NULL); //启动拯U程

  上面的例子中启动了两个线EEvaluateThread和CopyThreadQ线EEvaluateThread?0个数赋值给数组array1[]Q线ECopyThread数larray1[]拯l数larray2[]。由于数l的拯和赋值都是整体行为,如果不以互斥形式执行代码D:

for (int i = 0; i < 10; i++)
array1[i] = i;

  ?br>
for (int i = 0; i < 10; i++)
array2[i] = array1[i];

  其结果是很难预料的!

  除了可用CCriticalSection、CEvent、CMutex、CSemaphore作ؓU程间同步通信的方式以外,我们q可以利用PostThreadMessage函数在线E间发送消息:

BOOL PostThreadMessage(DWORD idThread, // thread identifier
UINT Msg, // message to post
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter

 

3.U程与消息队?br>
  在WIN32中,每一个线E都对应着一个消息队列。由于一个线E可以生数个窗口,所以ƈ不是每个H口都对应着一个消息队列。下列几句话应该作ؓ"定理"被记住:

  "定理" 一

  所有生给某个H口的消息,都先由创个窗口的U程处理Q?br>
  "定理" ?br>
  Windows屏幕上的每一个控仉是一个窗口,有对应的H口函数?br>
  消息的发送通常有两U方式,一是SendMessageQ一是PostMessageQ其原型分别为:

LRESULT SendMessage(HWND hWnd, // handle of destination window
 UINT Msg, // message to send
 WPARAM wParam, // first message parameter
 LPARAM lParam // second message parameter
);
BOOL PostMessage(HWND hWnd, // handle of destination window
 UINT Msg, // message to post
 WPARAM wParam, // first message parameter
 LPARAM lParam // second message parameter
);

  两个函数原型中的四个参数的意义相同,但是SendMessage和PostMessage的行为有差异。SendMessage必须{待消息被处理后才返回,而PostMessage仅仅消息放入消息队列。SendMessage的目标窗口如果属于另一个线E,则会发生U程上下文切换,{待另一U程处理完成消息。ؓ了防止另一U程当掉Q导致SendMessage永远不能q回Q我们可以调用SendMessageTimeout函数Q?br>
LRESULT SendMessageTimeout(
 HWND hWnd, // handle of destination window
 UINT Msg, // message to send
 WPARAM wParam, // first message parameter
 LPARAM lParam, // second message parameter
 UINT fuFlags, // how to send the message
 UINT uTimeout, // time-out duration
 LPDWORD lpdwResult // return value for synchronous call
);

  4. MFCU程、消息队列与MFCE序?生死因果"

  分析MFCE序的主U程启动及消息队列处理的q程有助于我们q一步理解UIU程与消息队列的关系Qؓ此我们需要简单地叙述一下MFCE序?生死因果"Q侯P《深入浅出MFC》)?br>
  使用VC++ 6.0的向导完成一个最单的单文档架构MFC应用E序MFCThreadQ?br>
  Q?Q?输入MFC EXE工程名MFCThreadQ?br>
  Q?Q?选择单文档架构,不支持Document/Viewl构Q?br>
  Q?Q?ActiveX?D container{其他选项都选择无?br>
  我们来分析这个工E。下面是产生的核心源代码Q?br>
  MFCThread.h 文g

class CMFCThreadApp : public CWinApp
{
 public:
  CMFCThreadApp();

  // Overrides
  // ClassWizard generated virtual function overrides
  //{{AFX_VIRTUAL(CMFCThreadApp)
   public:
    virtual BOOL InitInstance();
  //}}AFX_VIRTUAL

  // Implementation

 public:
  //{{AFX_MSG(CMFCThreadApp)
   afx_msg void OnAppAbout();
   // NOTE - the ClassWizard will add and remove member functions here.
   // DO NOT EDIT what you see in these blocks of generated code !
  //}}AFX_MSG
 DECLARE_MESSAGE_MAP()
};

  MFCThread.cpp文g

CMFCThreadApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CMFCThreadApp initialization

BOOL CMFCThreadApp::InitInstance()
{
 …
 CMainFrame* pFrame = new CMainFrame;
 m_pMainWnd = pFrame;

 // create and load the frame with its resources
 pFrame->LoadFrame(IDR_MAINFRAME,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE, NULL,NULL);
 // The one and only window has been initialized, so show and update it.
 pFrame->ShowWindow(SW_SHOW);
 pFrame->UpdateWindow();

 return TRUE;
}

  MainFrm.h文g

#include "ChildView.h"

class CMainFrame : public CFrameWnd
{
 public:
  CMainFrame();
 protected:
  DECLARE_DYNAMIC(CMainFrame)

  // Attributes
 public:

  // Operations
 public:
  // Overrides
  // ClassWizard generated virtual function overrides
  //{{AFX_VIRTUAL(CMainFrame)
   virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
   virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo);
  //}}AFX_VIRTUAL

  // Implementation
 public:
  virtual ~CMainFrame();
  #ifdef _DEBUG
   virtual void AssertValid() const;
   virtual void Dump(CDumpContext& dc) const;
  #endif
  CChildView m_wndView;

  // Generated message map functions
 protected:
 //{{AFX_MSG(CMainFrame)
  afx_msg void OnSetFocus(CWnd *pOldWnd);
  // NOTE - the ClassWizard will add and remove member functions here.
  // DO NOT EDIT what you see in these blocks of generated code!
 //}}AFX_MSG
 DECLARE_MESSAGE_MAP()
};

  MainFrm.cpp文g

IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
 //{{AFX_MSG_MAP(CMainFrame)
  // NOTE - the ClassWizard will add and remove mapping macros here.
  // DO NOT EDIT what you see in these blocks of generated code !
  ON_WM_SETFOCUS()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction

CMainFrame::CMainFrame()
{
 // TODO: add member initialization code here
}

CMainFrame::~CMainFrame()
{}

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
 if( !CFrameWnd::PreCreateWindow(cs) )
  return FALSE;
  // TODO: Modify the Window class or styles here by modifying
  // the CREATESTRUCT cs

 cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
 cs.lpszClass = AfxRegisterWndClass(0);
 return TRUE;
}

  ChildView.h文g

// CChildView window

class CChildView : public CWnd
{
 // Construction
 public:
  CChildView();

  // Attributes
 public:
  // Operations
 public:
  // Overrides
  // ClassWizard generated virtual function overrides
  //{{AFX_VIRTUAL(CChildView)
   protected:
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
  //}}AFX_VIRTUAL

  // Implementation
 public:
  virtual ~CChildView();

  // Generated message map functions
 protected:
  //{{AFX_MSG(CChildView)
   afx_msg void OnPaint();
  //}}AFX_MSG
 DECLARE_MESSAGE_MAP()
};

ChildView.cpp文g
// CChildView

CChildView::CChildView()
{}

CChildView::~CChildView()
{}

BEGIN_MESSAGE_MAP(CChildView,CWnd )
//}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CChildView message handlers

BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs)
{
 if (!CWnd::PreCreateWindow(cs))
  return FALSE;

 cs.dwExStyle |= WS_EX_CLIENTEDGE;
 cs.style &= ~WS_BORDER;
 cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW|CS_VREDRAW|CS_DBLCLKS,::LoadCursor(NULL, IDC_ARROW),
HBRUSH(COLOR_WINDOW+1),NULL);

 return TRUE;
}

void CChildView::OnPaint()
{
 CPaintDC dc(this); // device context for painting

 // TODO: Add your message handler code here
 // Do not call CWnd::OnPaint() for painting messages
}

  文gMFCThread.h和MFCThread.cpp定义和实现的cCMFCThreadAppl承自CWinAppc,而CWinAppcdl承自CWinThreadc(CWinThreadcdl承自CCmdTargetc)Q所以CMFCThread本质上是一个MFCU程c,下图l出了相关的cdơ结构:

 

我们提取CWinAppcd型的一部分Q?br>
class CWinApp : public CWinThread
{
 DECLARE_DYNAMIC(CWinApp)
 public:
  // Constructor
  CWinApp(LPCTSTR lpszAppName = NULL);// default app name
  // Attributes
  // Startup args (do not change)
  HINSTANCE m_hInstance;
  HINSTANCE m_hPrevInstance;
  LPTSTR m_lpCmdLine;
  int m_nCmdShow;
  // Running args (can be changed in InitInstance)
  LPCTSTR m_pszAppName; // human readable name
  LPCTSTR m_pszExeName; // executable name (no spaces)
  LPCTSTR m_pszHelpFilePath; // default based on module path
  LPCTSTR m_pszProfileName; // default based on app name

  // Overridables
  virtual BOOL InitApplication();
  virtual BOOL InitInstance();
  virtual int ExitInstance(); // return app exit code
  virtual int Run();
  virtual BOOL OnIdle(LONG lCount); // return TRUE if more idle processing
  virtual LRESULT ProcessWndProcException(CException* e,const MSG* pMsg);

 public:
  virtual ~CWinApp();
 protected:
  DECLARE_MESSAGE_MAP()
};

  SDKE序的WinMain 所完成的工作现在由CWinApp 的三个函数完成:

virtual BOOL InitApplication();
virtual BOOL InitInstance();
virtual int Run();

  "CMFCThreadApp theApp;"语句定义的全局变量theApp是整个程式的application objectQ每一个MFC 应用E序都有一个。当我们执行MFCThreadE序的时候,q个全局变量被构造。theApp 配置完成后,WinMain开始执行。但是程序中q没有WinMain的代码,它在哪里呢?原来MFC早已准备好ƈ由Linker直接加到应用E序代码中的Q其原型为(存在于VC++6.0安装目录下提供的APPMODUL.CPP文g中)Q?br>
extern "C" int WINAPI
_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
 // call shared/exported WinMain
 return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

  其中调用的AfxWinMain如下Q存在于VC++6.0安装目录下提供的WINMAIN.CPP文g中)Q?br>
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
 ASSERT(hPrevInstance == NULL);

 int nReturnCode = -1;
 CWinThread* pThread = AfxGetThread();
 CWinApp* pApp = AfxGetApp();

 // AFX internal initialization
 if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
  goto InitFailure;

 // App global initializations (rare)
 if (pApp != NULL && !pApp->InitApplication())
  goto InitFailure;

 // Perform specific initializations
 if (!pThread->InitInstance())
 {
  if (pThread->m_pMainWnd != NULL)
  {
   TRACE0("Warning: Destroying non-NULL m_pMainWndn");
   pThread->m_pMainWnd->DestroyWindow();
  }
  nReturnCode = pThread->ExitInstance();
  goto InitFailure;
 }
 nReturnCode = pThread->Run();

 InitFailure:
 #ifdef _DEBUG
  // Check for missing AfxLockTempMap calls
  if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
  {
   TRACE1("Warning: Temp map lock count non-zero (%ld).n",
AfxGetModuleThreadState()->m_nTempMapLock);
  }
  AfxLockTempMaps();
  AfxUnlockTempMaps(-1);
 #endif

 AfxWinTerm();
 return nReturnCode;
}

  我们提取dQ实际上Q这个函数做的事情主要是Q?br>
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)
pApp->InitApplication()
pThread->InitInstance()
pThread->Run();

  其中QInitApplication 是注册窗口类别的场所QInitInstance是生窗口ƈ昄H口的场所QRun是提取ƈ分派消息的场所。这PMFC同WIN32 SDKE序对应h了。CWinThread::Run是程序生命的"zL源头"Q侯P《深入浅出MFC》,函数存在于VC++ 6.0安装目录下提供的THRDCORE.CPP文g中)Q?br>
// main running routine until thread exits
int CWinThread::Run()
{
 ASSERT_VALID(this);

 // for tracking the idle time state
 BOOL bIdle = TRUE;
 LONG lIdleCount = 0;

 // acquire and dispatch messages until a WM_QUIT message is received.
 for (;;)
 {
  // phase1: check to see if we can do idle work
  while (bIdle && !::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))
  {
   // call OnIdle while in bIdle state
   if (!OnIdle(lIdleCount++))
    bIdle = FALSE; // assume "no idle" state
  }

  // phase2: pump messages while available
  do
  {
   // pump message, but quit on WM_QUIT
   if (!PumpMessage())
    return ExitInstance();

   // reset "no idle" state after pumping "normal" message
   if (IsIdleMessage(&m_msgCur))
   {
    bIdle = TRUE;
    lIdleCount = 0;
   }

  } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
 }
 ASSERT(FALSE); // not reachable
}

  其中的PumpMessage函数又对应于Q?br>
/////////////////////////////////////////////////////////////////////////////
// CWinThread implementation helpers

BOOL CWinThread::PumpMessage()
{
 ASSERT_VALID(this);

 if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
 {
  return FALSE;
 }

 // process this message
 if(m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
 
 return TRUE;
}

  因此Q忽略IDLE状态,整个RUN的执行提取主q就是:

do while (::PeekMessage(...));

  由此Q我们徏立了MFC消息获取和派生机制与WIN32 SDKE序之间的对应关pR下面l分析MFC消息?l行"q程?br>
  在MFC中,只要是CWnd 衍生cdQ就可以拦下MWindows消息。与H口无关的MFCcdQ例如CDocument 和CWinAppQ如果也惛_理消息,必须衍生自CCmdTargetQƈ且只可能收到WM_COMMAND消息。所有能q行MESSAGE_MAP的类都承自CCmdTargetQ如Q?br>

  MFC中MESSAGE_MAP的定义依赖于以下三个宏:

DECLARE_MESSAGE_MAP()

BEGIN_MESSAGE_MAP(
 theClass, //Specifies the name of the class whose message map this is
 baseClass //Specifies the name of the base class of theClass
)

END_MESSAGE_MAP()

  我们E序中涉及到的有QMFCThread.h、MainFrm.h、ChildView.h文g

DECLARE_MESSAGE_MAP()
MFCThread.cpp文g
BEGIN_MESSAGE_MAP(CMFCThreadApp, CWinApp)
//{{AFX_MSG_MAP(CMFCThreadApp)
ON_COMMAND(ID_APP_ABOUT, OnAppAbout)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
MainFrm.cpp文g
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code !
ON_WM_SETFOCUS()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
ChildView.cpp文g
BEGIN_MESSAGE_MAP(CChildView,CWnd )
//}AFX_MSG_MAP
END_MESSAGE_MAP()

  p些宏QMFC建立了一个消息映表Q消息流动网Q,按照消息动|匹配对应的消息处理函数Q完成整个消息的"l行"?br>
  看到q里怿你有q样的疑问:E序定义了CWinAppcȝtheApp全局变量Q可是从来没有调用AfxBeginThread或theApp.CreateThread启动U程呀QtheApp对应的线E是怎么启动的?

  {:MFC在这里用了很高明的一招。实际上Q程序开始运行,W一个线E是?a class=bluekey target=_blank>操作pȝQOSQ启动的Q在CWinApp的构造函数里QMFCtheApp"对应"向了q个U程Q具体的实现是这LQ?br>
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
 if (lpszAppName != NULL)
  m_pszAppName = _tcsdup(lpszAppName);
 else
  m_pszAppName = NULL;

 // initialize CWinThread state
 AFX_MODULE_STATE *pModuleState = _AFX_CMDTARGET_GETSTATE();
 AFX_MODULE_THREAD_STATE *pThreadState = pModuleState->m_thread;
 ASSERT(AfxGetThread() == NULL);
 pThreadState->m_pCurrentWinThread = this;
 ASSERT(AfxGetThread() == this);
 m_hThread = ::GetCurrentThread();
 m_nThreadID = ::GetCurrentThreadId();

 // initialize CWinApp state
 ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
 pModuleState->m_pCurrentWinApp = this;
 ASSERT(AfxGetApp() == this);

 // in non-running state until WinMain
 m_hInstance = NULL;
 m_pszHelpFilePath = NULL;
 m_pszProfileName = NULL;
 m_pszRegistryKey = NULL;
 m_pszExeName = NULL;
 m_pRecentFileList = NULL;
 m_pDocManager = NULL;
 m_atomApp = m_atomSystemTopic = NULL; //微Y懒鬼Q或者他认ؓ
 //q样q等含义更明?
 m_lpCmdLine = NULL;
 m_pCmdInfo = NULL;

 // initialize wait cursor state
 m_nWaitCursorCount = 0;
 m_hcurWaitCursorRestore = NULL;

 // initialize current printer state
 m_hDevMode = NULL;
 m_hDevNames = NULL;
 m_nNumPreviewPages = 0; // not specified (defaults to 1)

 // initialize DAO state
 m_lpfnDaoTerm = NULL; // will be set if AfxDaoInit called

 // other initialization
 m_bHelpMode = FALSE;
 m_nSafetyPoolSize = 512; // default size
}

  很显ӞtheApp成员变量都被赋予OS启动的这个当前线E相关的|如代码:

m_hThread = ::GetCurrentThread();//theApp的线E句柄等于当前线E句?
m_nThreadID = ::GetCurrentThreadId();//theApp的线EID{于当前U程ID

  所以CWinAppcd乎只是ؓMFCE序的第一个线E量w定制的Q它不需要也不能被AfxBeginThread或theApp.CreateThread"再次"启动。这是CWinAppcdtheApp全局变量的内涵!如果你要再增加一个UIU程Q不要承类CWinAppQ而应l承cCWinThread。而参考第1节,׃我们一般以ȝE(在MFCE序里实际上是OS启动的第一个线E)处理所有窗口的消息Q所以我们几乎没有再启动UIU程的需求!



Sandy 2008-11-02 22:36 发表评论
]]>
转:Windows多线E多d设计初步 http://www.shnenglu.com/SpringSnow/articles/65789.htmlSandySandySun, 02 Nov 2008 14:35:00 GMThttp://www.shnenglu.com/SpringSnow/articles/65789.htmlhttp://www.shnenglu.com/SpringSnow/comments/65789.htmlhttp://www.shnenglu.com/SpringSnow/articles/65789.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/65789.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65789.htmlhttp://www.shnenglu.com/ivenher/articles/983.html
QdaQ]当前行的Windows操作pȝQ它能同时运行几个程?独立q行的程序又UCE?Q对于同一个程序,它又可以分成若干个独立的执行,我们UC为线E,U程提供了多d处理的能力。用q程和线E的观点来研IY件是当今普遍采用的方法,q程和线E的概念的出玎ͼҎ高Y件的q行性有着重要的意义。现在的应用软g无一不是多线E多d处理Q单U城的Y件是不可惌的。因此掌握多U程多Q务设计方法对每个E序员都是必需要掌握的。本文针对多U程技术在应用中经帔R到的问题Q如U程间的通信、同步等Q对它们分别q行探讨?

   一?理解U程

   要讲解线E,不得不说一下进E,q程是应用程序的执行实例Q每个进E是q有的虚拟地址I间、代码、数据和其它pȝ资源l成。进E在q行时创建的资源随着q程的终止而死亡。线E的基本思想很简单,它是一个独立的执行,是进E内部的一个独立的执行单元Q相当于一个子E序Q它对应Visual C++中的CwinThreadcȝ对象。单独一个执行程序运行时Q缺省的q行包含的一个主U程Q主U程以函数地址的Ş式,如main或WinMain函数Q提供程序的启动点,当主U程l止Ӟq程也随之终止,但根据需要,应用E序又可以分解成许多独立执行的线E,每个U程q行的运行在同一q程中?

   一个进E中的所有线E都在该q程的虚拟地址I间中,使用该进E的全局变量和系l资源。操作系l给每个U程分配不同的CPU旉片,在某一个时刻,CPU只执行一个时间片内的U程Q多个时间片中的相应U程在CPU内轮执行,׃每个旉片时间很短,所以对用户来说Q仿佛各个线E在计算Z是ƈ行处理的。操作系l是ҎU程的优先来安排CPU的时_优先U高的线E优先运行,优先U低的线E则l箋{待?

   U程被分ZU:用户界面U程和工作线E(又称为后台线E)。用L面线E通常用来处理用户的输入ƈ响应各种事g和消息,其实Q应用程序的L行线ECWinAPP对象是一个用L面线E,当应用程序启动时自动创徏和启动,同样它的l止也意味着该程序的l束Q进城终止。工作者线E用来执行程序的后台处理dQ比如计、调度、对串口的读写操作等Q它和用L面线E的区别是它不用从CwinThreadcL生来创徏Q对它来说最重要的是如何实现工作U程d的运行控制函数。工作线E和用户界面U程启动时要调用同一个函数的不同版本Q最后需要读者明白的是,一个进E中的所有线E共享它们父q程的变量,但同时每个线E可以拥有自q变量?
   二?U程的管理和操作

   1Q?U程的启?

   创徏一个用L面线E,首先要从cCwinThread产生一个派生类Q同时必M用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE来声明和实现q个CwinThreadzcR?

   W二步是Ҏ需要重载该zcȝ一些成员函数如QExitInstance()QInitInstance()QOnIdle();PreTranslateMessage(){函敎ͼ最后启动该用户界面U程Q调用AfxBeginThread()函数的一个版本:CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );其中W一个参Cؓ指向定义的用L面线E类指针变量Q第二个参数为线E的优先U,W三个参CؓU程所对应的堆栈大,W四个参CؓU程创徏时的附加标志Q缺省ؓ正常状态,如ؓCREATE_SUSPENDED则线E启动后为挂L态?

   对于工作U程来说Q启动一个线E,首先需要编写一个希望与应用E序的其余部分ƈ行运行的函数如Fun1()Q接着定义一个指向CwinThread对象的指针变?pThread,调用AfxBeginThread(Fun1,param,priority)函数Q返回glpThread变量的同时一q启动该U程来执行上面的Fun1()函数Q其中Fun1是线E要q行的函数的名字Q也既是上面所说的控制函数的名字,param是准备传送给U程函数Fun1的Q?2位|priority则是定义该线E的优先U别Q它是预定义的常敎ͼ读者可参考MSDN?

   2Q线E的优先U?

   以下的CwinThreadcȝ成员函数用于U程优先U的操作Q?

int GetThreadPriority();
BOOL SetThradPriority()(int nPriority);

上述的二个函数分别用来获取和讄U程的优先Q这里的优先U,是相对于该线E所处的优先权层ơ而言的,处于同一优先权层ơ的U程Q优先高的U程先运行;处于不同优先权层ơ上的线E,谁的优先权层ơ高Q谁先运行。至于优先讄所需的常敎ͼ自己参考MSDN可以了Q要注意的是要想讄U程的优先Q这个线E在创徏时必d有THREAD_SET_INFORMATION讉K权限。对于线E的优先权层ơ的讄QCwinThreadcL有提供相应的函数Q但是可以通过Win32 SDK函数GetPriorityClass()和SetPriorityClass()来实现?

   3Q线E的悬挂、恢?

   CwinThreadcM包含了应用程序悬挂和恢复它所创徏的线E的函数Q其中SuspendThread()用来悬挂U程Q暂停线E的执行QResumeThread()用来恢复U程的执行。如果你对一个线E连l若q次执行SuspendThread()Q则需要连l执行相应次的ResumeThread()来恢复线E的q行?

   4Q结束线E?

   l止U程有三U途径Q线E可以在自n内部调用AfxEndThread()来终止自w的q行Q可以在U程的外部调用BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )来强行终止一个线E的q行Q然后调用CloseHandleQ)函数释放U程所占用的堆栈;W三U方法是改变全局变量QɾU程的执行函数返回,则该U程l止。下面以W三U方法ؓ例,l出部分代码Q?

////////////////////////////////////////////////////////////////
//////CtestView message handlers
/////Set to True to end thread
Bool bend=FALSE;//定义的全局变量Q用于控制线E的q行
//The Thread Function
UINT ThreadFunction(LPVOID pParam)//U程函数
{
while(!bend)
{Beep(100,100);
Sleep(1000);
}
return 0;
}
CwinThread *pThread;
HWND hWnd;
/////////////////////////////////////////////////////////////
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
pThread=AfxBeginThread(ThradFunction,hWnd);//启动U程
pThread->m_bAutoDelete=FALSE;//U程为手动删?
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{ bend=TRUE;//改变变量Q线E结?
WaitForSingleObject(pThread->m_hThread,INFINITE);//{待U程l束
delete pThread;//删除U程
Cview::OnDestroy();
}
   三?U程之间的通信

   通常情况下,一个次U线E要ZU程完成某种特定cd的Q务,q就隐含着表示在主U程和次U线E之间需要徏立一个通信的通道。一般情况下Q有下面的几U方法实现这U通信dQ用全局变量Q上一节的例子其实使用的就是这U方法)、用事件对象、用消息。这里我们主要介l后两种Ҏ?

   1Q?利用用户定义的消息通信

   在WindowsE序设计中,应用E序的每一个线E都拥有自己的消息队列,甚至工作U程也不例外Q这样一来,׃得线E之间利用消息来传递信息就变的非常单。首先用戯定义一个用h息,如下所C:#define WM_USERMSG WMUSER+100Q在需要的时候,在一个线E中调用

Q:PostMessage((HWND)param,WM_USERMSG,0,0)
?
CwinThread::PostThradMessage()

来向另外一个线E发送这个消息,上述函数的四个参数分别是消息要发送到的目的窗口的句柄、要发送的消息标志W、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的l果是在U程l束时显CZ个对话框Q提C线E结束:

UINT ThreadFunction(LPVOID pParam)
{
while(!bend)
{
Beep(100,100);
Sleep(1000);
}
Q:PostMessage(hWnd,WM_USERMSG,0,0)Q?
return 0;
}
////////WM_USERMSG消息的响应函CؓOnThreadended(WPARAM wParam,LPARAM lParam)
LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
{
AfxMessageBox("Thread ended.");
Retrun 0;
}

上面的例子是工作者线E向用户界面U程发送消息,对于工作者线E,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某U特定的处理{消息,让它在后台完成。在控制函数中可以直接用:QGetMessage()q个SDK函数q行消息分检和处理,自己实现一个消息@环。GetMessage()函数在判断该U程的消息队列ؓI时Q线E将pȝ分配l它的时间片让给其它U程Q不无效的占用CPU的时_如果消息队列不ؓI,p取这个消息,判断q个消息的内容ƈq行相应的处理?

   2Q用事g对象实现通信

   在线E之间传递信可行通信比较复杂的方法是使用事g对象Q用MFC的Ceventcȝ对象来表C。事件对象处于两U状态之一Q有信号和无信号Q线E可以监视处于有信号状态的事gQ以便在适当的时候执行对事g的操作。上qC子代码修改如下:

////////////////////////////////////////////////////////////////////
Cevent threadStart,threadEnd;
////////////////////////////////////////////////////////////////////
UINT ThreadFunction(LPVOID pParam)
{
Q:WaitForSingleObject(threadStart.m_hObject,INFINITE);
AfxMessageBox("Thread start.");
while(!bend)
{
Beep(100,100);
Sleep(1000);
Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
//{待threadEnd事g有信P无信hU程在这里悬?
If(result==Wait_OBJECT_0)
Bend=TRUE;
}
Q:PostMessage(hWnd,WM_USERMSG,0,0)Q?
return 0;
}
/////////////////////////////////////////////////////////////
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
threadStart.SetEvent();//threadStart事g有信?
pThread=AfxBeginThread(ThreadFunction,hWnd);//启动U程
pThread->m_bAutoDelete=FALSE;
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{ threadEnd.SetEvent();
WaitForSingleObject(pThread->m_hThread,INFINITE);
delete pThread;
Cview::OnDestroy();
}

q行q个E序Q当关闭E序Ӟ才显C提C框Q显C?Thread ended"
   四?U程之间的同?

   前面我们讲过Q各个线E可以访问进E中的公共变量,所以用多U程的过E中需要注意的问题是如何防止两个或两个以上的线E同时访问同一个数据,以免破坏数据的完整性。保证各个线E可以在一起适当的协调工作称为线E之间的同步。前面一节介l的事g对象实际上就是一U同步Ş式。Visual C++中用同步类来解x作系l的q行性而引L数据不安全的问题QMFC支持的七个多U程的同步类可以分成两大c:同步对象QCsyncObject、Csemaphore、Cmutex、CcriticalSection和CeventQ和同步讉K对象QCmultiLock和CsingleLockQ。本节主要介l界区Qcritical sectionQ、互斥(mutexeQ、信号量QsemaphoreQ,q些同步对象使各个线E协调工作,E序q行h更安全?

   1Q?临界?

   临界区是保证在某一个时间只有一个线E可以访问数据的Ҏ。用它的过E中Q需要给各个U程提供一个共享的临界区对象,无论哪个U程占有临界区对象,都可以访问受C护的数据Q这时候其它的U程需要等待,直到该线E释放界区对象为止Q界区被释攑֐Q另外的U程可以强占q个临界区,以便讉K׃n的数据。界区对应着一个CcriticalSection对象Q当U程需要访问保护数据时Q调用界区对象的Lock()成员函数Q当对保护数据的操作完成之后Q调用界区对象的Unlock()成员函数释放对界区对象的拥有权Q以使另一个线E可以夺取界区对象q访问受保护的数据。同时启动两个线E,它们对应的函数分别ؓWriteThread()和ReadThread()Q用以对公共数组larray[]操作Q下面的代码说明了如何用界区对象Q?

#include "afxmt.h"
int array[10],destarray[10];
CCriticalSection Section;
////////////////////////////////////////////////////////////////////////
UINT WriteThread(LPVOID param)
{Section.Lock();
for(int x=0;x<10;x++)
array[x]=x;
Section.Unlock();
}
UINT ReadThread(LPVOID param)
{
Section.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
Section.Unlock();
}

上述代码q行的结果应该是Destarray数组中的元素分别?-9Q而不是杂乱无章的敎ͼ如果不用同步,则不是这个结果,有兴的读者可以实验一下?
   2Q?互斥

   互斥与界区很相|但是使用时相对复杂一些,它不仅可以在同一应用E序的线E间实现同步Q还可以在不同的q程间实现同步,从而实现资源的安全׃n。互斥与Cmutexcȝ对象相对应,使用互斥对象Ӟ必须创徏一个CSingleLock或CMultiLock对象Q用于实际的讉K控制Q因里的例子只处理单个互斥,所以我们可以用CSingleLock对象Q该对象的Lock()函数用于占有互斥QUnlock()用于释放互斥。实C码如下:

#include "afxmt.h"
int array[10],destarray[10];
CMutex Section;

/////////////////////////////////////////////////////////////
UINT WriteThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
for(int x=0;x<10;x++)
array[x]=x;
singlelock.Unlock();
}
UINT ReadThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();

For(int x=0;x<10;x++)
Destarray[x]=array[x];
singlelock.Unlock();

}

   3Q?信号?

   信号量的用法和互斥的用法很相|不同的是它可以同一时刻允许多个U程讉K同一个资源,创徏一个信号量需要用Csemaphorecd明一个对象,一旦创Z一个信号量对象Q就可以用它来对资源的访问技术。要实现计数处理Q先创徏一个CsingleLock或CmltiLock对象Q然后用该对象的Lock()函数减少q个信号量的计数|Unlock()反之。下面的代码分别启动三个U程Q执行时同时昄二个消息框,然后10U后W三个消息框才得以显C?

/////////////////////////////////////////////////////////////////
Csemaphore *semaphore;
Semaphore=new Csemaphore(2,2);
HWND hWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
//////////////////////////////////////////////////////////////////////
UINT ThreadProc1(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);
return 0;
}
UINT ThreadProc2(LPVOID param)
{CSingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);
return 0;
}
UINT ThreadProc3(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);
return 0;
}


   对复杂的应用E序来说Q线E的应用l应用程序提供了高效、快速、安全的数据处理能力。本文讲qCU程中经帔R到的问题Q希望对读者朋友有一定的帮助?

Sandy 2008-11-02 22:35 发表评论
]]>
转:一个跨q_?C++ 内存泄漏器http://www.shnenglu.com/SpringSnow/articles/65742.htmlSandySandySun, 02 Nov 2008 02:56:00 GMThttp://www.shnenglu.com/SpringSnow/articles/65742.htmlhttp://www.shnenglu.com/SpringSnow/comments/65742.htmlhttp://www.shnenglu.com/SpringSnow/articles/65742.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/65742.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65742.html

http://www-128.ibm.com/developerworks/cn/linux/l-mleak2/index.html


内存泄漏对于C/C++E序员来说也可以作是个永恒的话题了吧。在Windows下,MFC的一个很有用的功能就是能在程序运行结束时报告是否发生了内存泄漏。在Linux下,相对来说没有那么容易用的解决Ҏ了:像mpatrol之类的现有工P易用性、附加开销和性能都不是很理想。本文实C个极易于使用、跨q_的C++内存泄漏器。ƈ对相关的技术问题作一下探讨?/p>

基本使用

对于下面q样的一个简单程序test.cppQ?/p>
int main()
            {
            int* p1 = new int;
            char* p2 = new char[10];
            return 0;
            }
            

我们的基本需求当然是对于该程序报告存在两处内存泄漏。要做到q点的话Q非常简单,只要把debug_new.cpp也编译、链接进d可以了。在Linux下,我们使用Q?/p>
g++ test.cpp debug_new.cpp -o test
            

输出l果如下所C:

Leaked object at 0x805e438 (size 10, <Unknown>:0)
            Leaked object at 0x805e410 (size 4, <Unknown>:0)
            

如果我们需要更清晰的报告,也很单,在test.cpp开头加一?/p>
#include "debug_new.h"
            

卛_。添加该行后的输出如下:

Leaked object at 0x805e438 (size 10, test.cpp:5)
            Leaked object at 0x805e410 (size 4, test.cpp:4)
            

非常单!





回页?/font>


背景知识

在new/delete操作中,C++为用户生了对operator new和operator delete的调用。这是用户不能改变的。operator new和operator delete的原型如下所C:

void *operator new(size_t) throw(std::bad_alloc);
            void *operator new[](size_t) throw(std::bad_alloc);
            void operator delete(void*) throw();
            void operator delete[](void*) throw();
            

对于"new int"Q编译器会生一个调?operator new(sizeof(int))"Q而对?new char[10]"Q编译器会?operator new[](sizeof(char) * 10)"Q如果new后面跟的是一个类名的话,当然q要调用该类的构造函敎ͼ。类似地Q对?delete ptr"?delete[] ptr"Q编译器会?operator delete(ptr)"调用?operator delete[](ptr)"调用Q如果ptr的类型是指向对象的指针的话,那在operator delete之前q要调用对象的析构函敎ͼ。当用户没有提供q些操作W时Q编译系l自动提供其定义Q而当用户自己提供了这些操作符Ӟp盖了~译pȝ提供的版本,从而可获得对动态内存分配操作的_跟踪和控制?/p>

同时Q我们还可以使用placement new操作W来调整operator new的行为。所谓placement newQ是指带有附加参数的new操作W,比如Q当我们提供了一个原型ؓ

void* operator new(size_t size, const char* file, int line);
            

的操作符Ӟ我们可以?new("hello", 123) int"来生一个调?operator new(sizeof(int), "hello", 123)"。这可以是相当灵zȝ。又如,C++标准要求~译器提供的一个placement new操作W是

void* operator new(size_t size, const std::nothrow_t&);
            

其中Qnothrow_t通常是一个空l构Q定义ؓ"struct nothrow_t {};"Q,其唯一目的是提供编译器一个可Ҏ重蝲规则识别具体调用的类型。用户一般简单地使用"new(std::nothrow) cd"Qnothrow是一个nothrow_tcd的常量)来调用这个placement new操作W。它与标准new的区别是Qnew在分配内存失败时会抛出异常,?new(std::nothrow)"在分配内存失败时会返回一个空指针?/p>

要注意的是,没有对应?delete(std::nothrow) ptr"的语法;不过后文会提到另一个相关问题?/p>

要进一步了解以上关于C++语言Ҏ的信息Q请参阅[Stroustrup1997]Q特别是6.2.6?0.4.11?5.6?9.4.5和B.3.4节。这些C++语言Ҏ是理解本实现的关键?/p>



回页?/font>


原?/span>

和其它一些内存泄漏检的方式cMQdebug_new中提供了operator new重蝲Qƈ使用了宏在用L序中q行替换。debug_new.h中的相关部分如下Q?/p>
void* operator new(size_t size, const char* file, int line);
            void* operator new[](size_t size, const char* file, int line);
            #define new DEBUG_NEW
            #define DEBUG_NEW new(__FILE__, __LINE__)
            

拿上面加入debug_new.h包含后的test.cpp来说Q?new char[10]"在预处理后会变成"new("test.cpp", 4) char[10]"Q编译器会据此生一?operator new[](sizeof(char) * 10, "test.cpp", 4)"调用。通过在debug_new.cpp中自定义"operator new(size_t, const char*, int)"?operator delete(void*)"Q以?operator new[]…"?operator delete[]…"Qؓ避免行文累赘Q以下不特别指出Q说到operator new和operator delete均同时包含数l版本)Q我可以跟踪所有的内存分配调用Qƈ在指定的查点上对不匹配的new和delete操作q行报警。实现可以相当简单,用map记录所有分配的内存指针可以了Qnew时往map里加一个指针及其对应的信息Qdelete时删除指针及对应的信息;delete时如果map里不存在该指针ؓ错误删除Q程序退出时如果map里还存在未删除的指针则说明有内存泄漏?/p>

不过Q如果不包含debug_new.hQ这U方法就起不了作用了。不仅如此,部分文g包含debug_new.hQ部分不包含debug_new.h都是不可行的。因然我们用了两种不同的operator new --"operator new(size_t, const char*, int)"?operator new(size_t)"-- 但可用的"operator delete"q是只有一U!使用我们自定义的"operator delete"Q当我们删除?operator new(size_t)"分配的指针时Q程序将认ؓ被删除的是一个非法指针!我们处于一个两隑֢圎ͼ要么对这U情况生误报,要么寚w复删除同一指针两次不予报警Q都不是可接受的良好行ؓ?/p>

看来Q自定义全局"operator new(size_t)"也是不可避免的了。在debug_new中,我是q样做的Q?/p>
void* operator new(size_t size)
            {
            return operator new(size, "<Unknown>", 0);
            }
            

但前面描q的方式d现内存泄漏检器Q在某些C++的实CQ如GCC 2.95.3中带的SGI STLQ工作正常,但在另外一些实C会莫名其妙地崩溃。原因也不复杂,SGI STL使用了内存池Q一ơ分配一大片内存Q因而利用map成ؓ可能Q但在其他的实现可能没这样做Q在map中添加数据会调用operator newQ而operator new会在map中添加数据,从而构成一个死循环Q导致内存溢出,应用E序立即崩溃。因此,我们不得不停止用方便的STL模板Q而用手工构建的数据l构Q?/p>
struct new_ptr_list_t
            {
            new_ptr_list_t*		next;
            const char*			file;
            int					line;
            size_t				size;
            };
            

我最初的实现Ҏ是每次在用new分配内存Ӟ调用malloc多分?sizeof(new_ptr_list_t) 个字节,把分配的内存全部串成一个一个链表(利用next字段Q,把文件名、行受对象大信息分别存入file、line和size字段中,然后q回(mallocq回的指?+ sizeof(new_ptr_list_t))。在deleteӞ则在链表中搜索,如果扑ֈ的话Q?char*)链表指针 + sizeof(new_ptr_list_t) == 待释攄指针Q,则调整链表、释攑ֆ存,找不到的话报告删除非法指针ƈabort?/p>

至于自动内存泄漏,我的做法是生成一个静态全局对象Q根据C++的对象生命期Q在E序初始化时会调用该对象的构造函敎ͼ在其退出时会调用该对象的析构函敎ͼQ在其析构函C调用内存泄漏的函数。用h工调用内存泄漏检函数当然也是可以的?/p>

基本实现大体是如此?/p>



回页?/font>


可用性改q?/span>

上述Ҏ最初工作得相当好,直到我开始创建大量的对象为止。由于每ơdelete旉要在链表中进行搜索,q_搜烦ơ数?链表长度/2)Q程序很快就慢得像乌龟爬。虽说只是用于调试,速度太慢也是不能接受的。因此,我做了一个小更改Q把指向链表头部的new_ptr_listҎ了一个数l,一个对象指针放在哪一个链表中则由它的哈希值决定?-用户可以更改宏DEBUG_NEW_HASH和DEBUG_NEW_HASHTABLESIZE的定义来调整debug_new的行为。他们的当前值是我测试下来比较满意的定义?/p>

使用中我们发玎ͼ在某些特D情况下Q请直接参看debug_new.cpp中关于DEBUG_NEW_FILENAME_LEN部分的注释)Q文件名指针会失效。因此,目前的debug_new的缺省行Z复制文g名的?0个字W,而不只是存储文g名的指针。另外,h意原先new_ptr_list_t的长度ؓ16字节Q现在是32字节Q都能保证在通常情况下内存对齐?/p>

此外Qؓ了允许程序能?new(std::nothrow) 一起工作,我也重蝲了operator new(size_t, const std::nothrow_t&) throw()Q不然的话,debug_new会认为对应于 new(nothrow) 的delete调用删除的是一个非法指针。由于debug_new不抛出异常(内存不时程序直接报警退出)Q所以这一重蝲的操作只不过是调?operator new(size_t) 而已。这׃用多说了?/p>

前面已经提到Q要得到_的内存泄漏检报告,可以在文件开头包?debug_new.h"。我的惯常做法可以用作参考:

#ifdef _DEBUG
            #include "debug_new.h"
            #endif
            

包含的位|应当尽可能早,除非跟系l的头文Ӟ典型情况是STL的头文gQ发生了冲突。在某些情况下,可能会不希望debug_new重定义newQ这时可以在包含debug_new.h之前定义DEBUG_NEW_NO_NEW_REDEFINITIONQ这L话,在用户应用程序中应用debug_new来代替newQ顺便提一句,没有定义DEBUG_NEW_NO_NEW_REDEFINITION时也可以使用debug_new代替newQ。在源文件中也许pq样写:

#ifdef _DEBUG
            #define DEBUG_NEW_NO_NEW_REDEFINITION
            #include "debug_new.h"
            #else
            #define debug_new new
            #endif
            

q在需要追t内存分配的时候全部用debug_newQ考虑使用全局替换Q?/p>

用户可以选择定义DEBUG_NEW_EMULATE_MALLOCQ这样debug_new.h会用debug_new和delete来模拟malloc和free操作Q得用L序中的malloc和free操作也可以被跟踪。在使用某些~译器的时候(如Digital Mars C++ Compiler 8.29和Borland C++ Compiler 5.5.1Q,用户必须定义NO_PLACEMENT_DELETEQ否则编译无法通过。用戯可以使用两个全局布尔量来调整debug_new的行为:new_verbose_flagQ缺省ؓfalseQ定义ؓtrue时能在每ơnew/delete时向标准错误输出昄跟踪信息Qnew_autocheck_flagQ缺省ؓtrueQ即在程序退出时自动调用check_leaks查内存泄漏,改ؓfalse的话用户必须手工调用check_leaks来检查内存泄漏?/p>

需要注意的一ҎQ由于自动调用check_leaks是在debug_new.cpp中的静态对象析构时Q因此不能保证用L全局对象的析构操作发生在check_leaks调用之前。对于Windows上的MSVCQ我使用?#pragma init_seg(lib)"来调整对象分配释攄序Q但很遗憾,我不知道在其他的一些编译器中(特别是,我没能成功地在GCC中解册一问题Q怎么做到q一炏Vؓ了减误报警Q我采取的方式是在自动调用了check_leaks之后设new_verbose_flag为trueQ这Pq误报告了内存泄漏Q随后的delete操作q是会被打印昄出来。只要泄漏报告和delete报告的内容一_我们仍可以判断出没有发生内存泄漏?/p>

Debug_new也能对同一指针重复调用deleteQ或delete无效指针Q的错误。程序将昄错误的指针|q强制调用abort退出?/p>

q有一个问题是异常处理。这值得用专门的一节来q行说明?/p>



回页?/font>


构造函C的异?/span>

我们看一下以下的单程序示例:

#include <stdexcept>
            #include <stdio.h>
            void* operator new(size_t size, int line)
            {
            printf("Allocate %u bytes on line %d\\n", size, line);
            return operator new(size);
            }
            class Obj {
            public:
            Obj(int n);
            private:
            int _n;
            };
            Obj::Obj(int n) : _n(n)
            {
            if (n == 0) {
            throw std::runtime_error("0 not allowed");
            }
            }
            int main()
            {
            try {
            Obj* p = new(__LINE__) Obj(0);
            delete p;
            } catch (const std::runtime_error& e) {
            printf("Exception: %s\\n", e.what());
            }
            }
            

看出代码中有什么问题了吗?实际上,如果我们用MSVC~译的话Q编译器的警告信息已l告诉我们发生了什么:

test.cpp(27) : warning C4291: 'void *__cdecl operator new(unsigned int,int)' :
            no matching operator delete found; memory will not be freed if initialization throws an exception
            

好,把debug_new.cpp链接q去。运行结果如下:

Allocate 4 bytes on line 27 Exception: 0 not allowed Leaked object at 00342BE8 (size 4, <Unknown>:0) 	

啊哦Q内存泄漏了不是Q?/p>

当然Q这U情况ƈ非很常见。可是,随着对象来复杂,谁能够保证一个对象的子对象的构造函数或者一个对象在构造函C调用的所有函数都不会抛出异常Qƈ且,解决该问题的Ҏq不复杂Q只是需要编译器?C++ 标准有较好支持,允许用户定义 placement delete 符Q[C++1998]Q?.3.4节;|上可以扑ֈ1996q的标准草案Q比如下面的|址 http://www.comnets.rwth-aachen.de/doc/c++std/expr.html#expr.newQ。在我测试的~译器中QGCCQ?.95.3或更高版本,Linux/WindowsQ和MSVCQ?.0或更高版本)没有问题Q而Borland C++ Compiler 5.5.1和Digital Mars C++ CompilerQ到v8.38为止的所有版本)则不支持该项Ҏ。在上面的例子中Q如果编译器支持的话Q我们就需要声明ƈ实现 operator delete(void*, int) 来回收new分配的内存。编译器不支持的话,需要用宏让编译器忽略相关的声明和实现。如果要让debug_new在Borland C++ Compiler 5.5.1或Digital Mars C++ Compiler下编译的话,用户必须定义宏NO_PLACEMENT_DELETEQ当Ӟ用户得自己注意小心构造函C抛出异常q个问题了?





回页?/font>


Ҏ比较

IBM developerWorks上刊载了z琨先生设计实现的一个Linux上的内存泄漏方法([z琨2003]Q。我的方案与其相比,主要区别如下Q?/p>

优点Q?

  • 跨^収ͼ只用标准函敎ͼq且在GCC 2.95.3/3.2QLinux/WindowsQ、MSVC 6、Digital Mars C++ 8.29、Borland C++ 5.5.1{多个编译器下调试通过。(虽然Linux是我的主要开发^収ͼ但我发现Q有时候能在Windows下编译运行代码还是非常方便的。)
  • 易用性:׃重蝲了operator new(size_t)--z琨先生只重载了operator new(size_t, const char*, int)--即不包含我的头文g也能内存泄漏;E序退出时能自动检内存泄漏;可以用L序(不包括系l?库文Ӟ中malloc/free产生的内存泄漏?
  • 灉|性:有多个灵zȝ可配|项Q可使用宏定义进行编译时选择?
  • 可重入性:不用全局变量Q没有嵌套delete问题?
  • 异常安全性:在编译器支持的情况下Q能够处理构造函C抛出的异常而不发生内存泄漏?

 

~点Q?

  • 单线E模型:跨^台的多线E实现较为麻烦,Ҏ目的实际需要,也ؓ了代码清晰简单v见,我的Ҏ不是U程安全的;换句话说Q如果多个线E中同时q行new或delete操作的话Q后果未定义?
  • 未实现运行中内存泄漏报告机Ӟ没有遇到q个需求JQ不q,如果要手工调用check_leaks函数实现的话也不困难Q只是跨q_性就有点问题了?
  • 不能带 [] 符和不?[] 符L的不匚wQ主要也是需求问题(如果要修改实现的话ƈ不困难)?
  • 不能在错误的delete调用时显C文件名和行P应该不是大问题;׃我重载了operator new(size_t)Q可以保证delete出错时程序必然有问题Q因而我不只是显C告信息,而且会强制程序abortQ可以通过跟踪E序、检查abort时程序的调用栈知道问题出在哪ѝ?

 

另外Q现在已存在不少商业和Open Source的内存泄漏检器Q本文不打算一一再做比较。Debug_new与它们相比,功能上ȝ来说仍较弱,但是Q其良好的易用性和跨^台性、低廉的附加开销q是h很大优势的?/p>



回页?/font>


ȝ和讨?/span>

以上D落基本上已l说明了debug_new的主要特炏V下面做一个小的ȝ?/p>

重蝲的算W:

  • operator new(size_t, const char*, int)
  • operator new[](size_t, const char*, int)
  • operator new(size_t)
  • operator new[](size_t)
  • operator new(size_t, const std::nothrow_t&)
  • operator new[](size_t, const std::nothrow_t&)
  • operator delete(void*)
  • operator delete[](void*)
  • operator delete(void*, const char*, int)
  • operator delete[](void*, const char*, int)
  • operator delete(void*, const std::nothrow_t&)
  • operator delete[](void*, const std::nothrow_t&)

 

提供的函敎ͼ

  • check_leaks()
    查是否发生内存泄?

 

提供的全局变量

  • new_verbose_flag
    是否在new和delete?|嗦"地显CZ?
  • new_autocheck_flag
    是否在程序退出是自动一ơ内存泄?

 

可重定义的宏Q?

  • NO_PLACEMENT_DELETE
    假设~译器不支持placement deleteQ全局有效Q?
  • DEBUG_NEW_NO_NEW_REDEFINITION
    不重定义newQ假讄户会自己使用debug_newQ包含debug_new.h时有效)
  • DEBUG_NEW_EMULATE_MALLOC
    重定义malloc/freeQ用new/deleteq行模拟Q包含debug_new.h时有效)
  • DEBUG_NEW_HASH
    改变内存块链表哈希值的法Q编译debug_new.cpp时有效)
  • DEBUG_NEW_HASHTABLE_SIZE
    改变内存块链表哈希桶的大(~译debug_new.cpp时有效)
  • DEBUG_NEW_FILENAME_LEN
    如果在分配内存时复制文g名的话,保留的文件名长度Qؓ0时则自动定义DEBUG_NEW_NO_FILENAME_COPYQ编译debug_new.cpp时有效;参见文g中的注释Q?
  • DEBUG_NEW_NO_FILENAME_COPY
    分配内存时不q行文g名复Ӟ而只是保存其指针Q效率较高(~译debug_new.cpp时有效;参见文g中的注释Q?

 

我本为,debug_new目前的一个主要缺h不支持多U程。对于某一特定q_Q要加入多线E支持ƈ不困难,隑ְ隑֜通用上(当然Q条件编译是一个办法,虽然不够优雅Q。等到C++标准中包含线E模型时Q这个问题也许能比较完美地解军_。另一个办法是使用像boostq样的程序库中的U程装c,不过Q这又会增加对其它库的依赖?-毕竟boostq不是C++标准的一部分。如果项目本wƈ不用boostQ单Zq一个目的用另外一个程序库gq不值得。因此,我自己暂时就不做q进一步的改进了?/p>

另外一个可能的修改是保留标准operator new的异常行为,使其在内存不的情况下抛出异常(普通情况)或是q回NULLQnothrow情况Q,而不是像现在一L止程序运行(参见debug_new.cpp的源代码Q。这一做法的难度主要在于后者:我没惛_什么方法,可以保留 new(nothrow) 的语法,同时能够报告文g名和行号Qƈ且还能够使用普通的new。不q,如果不用标准语法,一律用debug_new和debug_new_nothrow的话Q那q是非常Ҏ实现的?/p>

如果大家有改q意见或其它x的话Q欢q来信讨论?/p>

debug_new 的源代码目前可以?dbg_new.zip处下载?

在这文章的写完之后Q我l于q是实现了一个线E安全的版本。该版本使用了一个轻量的跨q_互斥体类fast_mutexQ目前支持Win32和POSIXU程Q在使用GCCQLinux/MinGWQ、MSVC时能通过命o行参数自动检线E类型)。有兴趣的话可在 http://mywebpage.netscape.com/yongweiwu/dbg_new.tgz下蝲?



参考资?

[C++1998] ISO/IEC 14882. Programming Languages-C++, 1st Edition. International Standardization Organization, International Electrotechnical Commission, American National Standards Institute, and Information Technology Industry Council, 1998

[Stroustrup1997] Bjarne Stroustrup. The C++ Programming Language, 3rd Edition. Addison-Wesley, 1997

[z琨2003] z琨?《如何在 linux 下检内存泄漏?/font>QIBM developerWorks 中国|站?



Sandy 2008-11-02 10:56 发表评论
]]>
转:谈内存泄漏Q二Q?/title><link>http://www.shnenglu.com/SpringSnow/articles/65740.html</link><dc:creator>Sandy</dc:creator><author>Sandy</author><pubDate>Sun, 02 Nov 2008 02:54:00 GMT</pubDate><guid>http://www.shnenglu.com/SpringSnow/articles/65740.html</guid><wfw:comment>http://www.shnenglu.com/SpringSnow/comments/65740.html</wfw:comment><comments>http://www.shnenglu.com/SpringSnow/articles/65740.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.shnenglu.com/SpringSnow/comments/commentRss/65740.html</wfw:commentRss><trackback:ping>http://www.shnenglu.com/SpringSnow/services/trackbacks/65740.html</trackback:ping><description><![CDATA[<a >http://www.vczx.com/article/show.php?id=68</a><br><br>内存泄漏: <br>内存泄漏的关键是要能截获住对分配内存和释放内存的函数的调用。截获住q两个函敎ͼ我们p跟踪每一块内存的生命周期Q比如,每当成功的分配一块内存后Q就把它的指针加入一个全局的list中;每当释放一块内存,再把它的指针从list中删除。这P当程序结束的时候,list中剩余的指针是指向那些没有被释攄内存。这里只是简单的描述了检内存泄漏的基本原理Q详l的法可以参见Steve Maguire?lt;<Writing Solid Code>>?<br>如果要检堆内存的泄漏,那么需要截获住malloc/realloc/free和new/delete可以了Q其实new/delete最l也是用malloc/free的,所以只要截获前面一l即可)。对于其他的泄漏Q可以采用类似的ҎQ截获住相应的分配和释放函数。比如,要检BSTR的泄漏,需要截获SysAllocString/SysFreeStringQ要HMENU的泄漏,需要截获CreateMenu/ DestroyMenu。(有的资源的分配函数有多个Q释攑և数只有一个,比如QSysAllocStringLen也可以用来分配BSTRQ这时就需要截获多个分配函敎ͼ <br>在Windowsq_下,内存泄漏的工具常用的一般有三种QMS C-Runtime Library内徏的检功能;外挂式的工P诸如QPurifyQBoundsChecker{;利用Windows NT自带的Performance Monitor。这三种工具各有优缺点,MS C-Runtime Library虽然功能上较之外挂式的工兯弱,但是它是免费的;Performance Monitor虽然无法标示出发生问题的代码Q但是它能检出隐式的内存泄漏的存在Q这是其他两cdh能ؓ力的地方?<br>以下我们详细讨论q三U检工P <br>VC下内存泄漏的方?<br>用MFC开发的应用E序Q在DEBUG版模式下~译后,都会自动加入内存泄漏的检代码。在E序l束后,如果发生了内存泄漏,在DebugH口中会昄出所有发生泄漏的内存块的信息Q以下两行显CZ一块被泄漏的内存块的信息: <br>E:\TestMemLeak\TestDlg.cpp(70)     : {59} normal block at 0x00881710, 200 bytes long. <br>Data: <abcdefghijklmnop>     61 62 63 64 65 66 67 68 69 6A 6B 6C 6D 6E 6F 70 <br>W一行显C内存块由TestDlg.cpp文gQ第70行代码分配,地址?x00881710Q大ؓ200字节Q{59}是指调用内存分配函数的Request OrderQ关于它的详l信息可以参见MSDN中_CrtSetBreakAlloc()的帮助。第二行昄该内存块?6个字节的内容Q尖括号内是以ASCII方式昄Q接着的是?6q制方式昄?<br>一般大安误以些内存泄漏的功能是由MFC提供的,其实不然。MFC只是装和利用了MS C-Runtime Library的Debug Function。非MFCE序也可以利用MS C-Runtime Library的Debug Function加入内存泄漏的检功能。MS C-Runtime Library在实现malloc/freeQstrdup{函数时已经内徏了内存泄漏的功能?<br>注意观察一下由MFC Application Wizard生成的项目,在每一个cpp文g的头部都有这样一D宏定义Q?<br>#ifdef     _DEBUG <br>#define     new DEBUG_NEW <br>#undef     THIS_FILE <br>static     char THIS_FILE[] = __FILE__; <br>#endif <br>有了q样的定义,在编译DEBUG版时Q出现在q个cpp文g中的所有new都被替换成DEBUG_NEW了。那么DEBUG_NEW是什么呢QDEBUG_NEW也是一个宏Q以下摘自afx.hQ?632?<br>#define     DEBUG_NEW new(THIS_FILE, __LINE__) <br>所以如果有q样一行代码: <br>char*     p = new char[200]; <br>l过宏替换就变成了: <br>char*     p = new( THIS_FILE, __LINE__)char[200]; <br>ҎC++的标准,对于以上的new的用方法,~译器会Lq样定义的operator newQ?<br>void*     operator new(size_t, LPCSTR, int) <br>我们在afxmem.cpp 63行找C一个这Loperator new 的实?<br>void*     AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine) <br>{ <br>    return     ::operator new(nSize, _NORMAL_BLOCK, lpszFileName, nLine); <br>} <br>void*     __cdecl operator new(size_t nSize, int nType, LPCSTR lpszFileName, int     nLine) <br>{ <br>    … <br>           pResult = _malloc_dbg(nSize, nType,     lpszFileName, nLine); <br>           if (pResult != NULL) <br>               return pResult; <br>    … <br>} <br>W二个operator new函数比较长,Z单期_我只摘录了部分。很昄最后的内存分配q是通过_malloc_dbg函数实现的,q个函数属于MS C-Runtime Library 的Debug Function。这个函C但要求传入内存的大小Q另外还有文件名和行号两个参数。文件名和行号就是用来记录此ơ分配是由哪一D代码造成的。如果这块内存在E序l束之前没有被释放,那么q些信息׃输出到DebugH口里?<br>q里Z提一下THIS_FILEQ__FILE和__LINE__。__FILE__和__LINE__都是~译器定义的宏。当到__FILE__Ӟ~译器会把__FILE__替换成一个字W串Q这个字W串是当前在编译的文g的\径名。当到__LINE__Ӟ~译器会把__LINE__替换成一个数字,q个数字是当前q行代码的行受在DEBUG_NEW的定义中没有直接使用__FILE__Q而是用了THIS_FILEQ其目的是ؓ了减目标文件的大小。假讑֜某个cpp文g中有100处用了newQ如果直接用__FILE__Q那~译器会产生100个常量字W串Q这100个字W串都是q个cpp文g的\径名Q显然十分冗余。如果用THIS_FILEQ编译器只会产生一个常量字W串Q那100处new的调用用的都是指向帔R字符串的指针?<br>再次观察一下由MFC Application Wizard生成的项目,我们会发现在cpp文g中只对new做了映射Q如果你在程序中直接使用malloc函数分配内存Q调用malloc的文件名和行h不会被记录下来的。如果这块内存发生了泄漏QMS C-Runtime Library仍然能检到Q但是当输出q块内存块的信息Q不会包含分配它的的文g名和行号?<br>要在非MFCE序中打开内存泄漏的检功能非常容易,你只要在E序的入口处加入以下几行代码Q?<br>int     tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG ); <br>tmpFlag     |= _CRTDBG_LEAK_CHECK_DF; <br>_CrtSetDbgFlag(     tmpFlag ); <br>q样Q在E序l束的时候,也就是winmainQmain或dllmain函数q回之后Q如果还有内存块没有释放Q它们的信息会被打印到DebugH口里?<br>如果你试着创徏了一个非MFC应用E序Q而且在程序的入口处加入了以上代码Qƈ且故意在E序中不释放某些内存块,你会在DebugH口里看C下的信息Q?<br>{47}     normal block at 0x00C91C90, 200 bytes long. <br>Data: <            > 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F <br>内存泄漏的确到了,但是和上面MFCE序的例子相比,~少了文件名和行受对于一个比较大的程序,没有q些信息Q解决问题将变得十分困难?<br>Z能够知道泄漏的内存块是在哪里分配的,你需要实现类似MFC的映功能,把newQmaolloc{函数映到_malloc_dbg函数上。这里我不再赘述Q你可以参考MFC的源代码?<br>׃Debug Function实现在MS C-RuntimeLibrary中,所以它只能到堆内存的泄漏Q而且只限于mallocQrealloc或strdup{分配的内存Q而那些系l资源,比如HANDLEQGDI ObjectQ或是不通过C-Runtime Library分配的内存,比如VARIANTQBSTR的泄漏,它是无法到的,q是q种法的一个重大的局限性。另外,Z能记录内存块是在哪里分配的,源代码必ȝ应的配合Q这在调试一些老的E序非常ȝQ毕竟修Ҏ代码不是一件省心的事,q是q种法的另一个局限性?<br>对于开发一个大型的E序QMS C-Runtime Library提供的检功能是q远不够的。接下来我们q看外挂式的检工兗我用的比较多的是BoundsCheckerQ一则因为它的功能比较全面,更重要的是它的稳定性。这cd具如果不E_Q反而会忙里Mؕ。到底是鼎鼎大名的NuMegaQ我用下来基本上没有什么大问题?<br> <img src ="http://www.shnenglu.com/SpringSnow/aggbug/65740.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.shnenglu.com/SpringSnow/" target="_blank">Sandy</a> 2008-11-02 10:54 <a href="http://www.shnenglu.com/SpringSnow/articles/65740.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>转:内存泄露?http://www.shnenglu.com/SpringSnow/articles/65739.htmlSandySandySun, 02 Nov 2008 02:53:00 GMThttp://www.shnenglu.com/SpringSnow/articles/65739.htmlhttp://www.shnenglu.com/SpringSnow/comments/65739.htmlhttp://www.shnenglu.com/SpringSnow/articles/65739.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/65739.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65739.html
http://www.shnenglu.com/Ipedo/archive/2005/10/27/867.aspx

 cQ+中检内存泄漏可以引入系l定义的宏来查看Q内存在哪个位置泄漏

文g开始处加入下列定义
#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

E序退出时加入以下函数Q?br>
_CrtDumpMemoryLeaks();

如果有泄漏会昄
e:\myproject\mltithrd.14\mltithrd.cpp(95) : {68} client block at 0x00372550, subtype c0, 144 bytes long.
a CMultiDocTemplate object at $00372550, 144 bytes long


Sandy 2008-11-02 10:53 发表评论
]]>
转:内存泄露的工具Qdebugnewhttp://www.shnenglu.com/SpringSnow/articles/65738.htmlSandySandySun, 02 Nov 2008 02:52:00 GMThttp://www.shnenglu.com/SpringSnow/articles/65738.htmlhttp://www.shnenglu.com/SpringSnow/comments/65738.htmlhttp://www.shnenglu.com/SpringSnow/articles/65738.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/65738.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/65738.html 

内存泄露的工具Qdebugnew
http://dev.csdn.net/article/58/58407.shtm

|上有一个流传甚q的内存泄露的工具Qdebugnew(debugnew.h/debugnew.cpp)
用法很简单,把debugnew.cpp攑֜目里一L译,需要检的文g把debugnew.h嵌在文g的最前面?br>
为方便用,Ҏ代码做了一些小的改动?br>
下面是一些简单的说明Q?/p>

1、new 的重?/font>
void* operator new (size_t size, const char* file, int line)Q        ?br>void* operator new [] (size_t size, const char*  file, int line)Q      ?/p>

在需要检的文g里,重定义new
#define new new(__FILE__, __LINE__)

造成的结果:
ClassName *p = new ClassName; => ClassName *p = new(__FILE__, __LINE__) ClassName;
// 实际会调用void* operator new (size_t size, const char* file, int line)Q?/p>

ClassName **pp = new classname[count]; => ClassName **pp = new(__FILE__, __LINE__) ClassName[count];
// 实际会调用void* operator new [] (size_t size, const char*  file, int line)Q?/p>

q其实是利用了placement new的语法,通过一个简单的宏,可以把普通的new操作对应到相应的重蝲( ??)上去?/p>

2、delete 的重?/strong>
void operator delete (void* p, const char* file, int line);                   ?br>void operator delete [] (void* p, const char* file, int line);                ?br>void operator delete (void* p);                                               ?br>void operator delete [] (void* p);                                            ?/p>

因ؓ没有cM于placement new的语法,所以就不能用一个宏来替换替换delete了。要调用带有更多信息的delete操作W,只能修改源代码了?br>delete p; => delete ( p, __FILE__, __LINE__ );
delete []pp; => delete [] ( pp, __FILE__, __LINE__ );
但这个工作很烦琐Q如果ƈ不需要多余的信息的话Q简单地重蝲delete( ??)可以了?/p>

3、检和l计
E序开始时Q在debugnew.cpp中会创徏一个DebugNewTracer对象
在重载的new操作W? ??)中,每一ơ内存分配都会被记录Q而在delete( ????)中则会删除相应的记录?br>当程序结束,DebugNewTracer对象被销毁,它的析构函数会dump剩余的记录,q就是泄露的内存了?/p>

在原有代码的基础上,增加了记录size的功能,q样可以在每ơnew和deleteӞ看到实际占用的内存。所有信息可以dump出来Q也可以写入log?br>

5、源代码

********************************************************
debugnew.h:

/*
 filename: debugnew.h
 
 This code is based on code retrieved from a web site. The
 author was not identified, so thanks go to anonymous.

 This is used to substitute a version of the new operator that
 can be used for debugging memory leaks. To use it:
 
 - In any (all?) code files #include debugnew.h. Make sure all
  system files (i.e. those in <>'s) are #included before
  debugnew.h, and that any header files for your own code
  are #included after debugnew.h. The reason is that some
  system files refer to ::new, and this will not compile
  if debugnew is in effect. You may still have problems
  if any of your own code refers to ::new, or if any
  of your own files #include system files that use ::new
  and which have not already been #included before
  debugnew.h.
 - Add debugnew.cpp to the CodeWarrior project or compile
  it into your Linux executable. If debugnew.cpp is in the
  project, then debugnew.h must be #included in at least
  one source file
*/

#ifndef __DEBUGNEW_H__
#define __DEBUGNEW_H__

#include <map>

#define LOG_FILE

#if defined(LOG_FILE)
#define LOG_FILE_NAME "./debugnew.log"
#endif

void* operator new (std::size_t size, const char* file, int line);
void operator delete (void* p, const char* name, int line);
void* operator new [] (std::size_t size, const char* file, int line);
void operator delete [] (void* p, const char* name, int line);

class DebugNewTracer  {
private:
 
 class Entry  {
 public:
  Entry (char const* file, int line) : _file (file), _line (line) {}
  Entry (char const* file, int line, int size) : _file (file), _line (line), _size (size) {}
  Entry () : _file (0), _line (0), _size (0) {}
  const char* File () const { return _file; }
  int Line () const { return _line; }
  size_t Size () const { return _size; }
 private:
  char const* _file;
  int _line;
  size_t _size;
 };

 class Lock {
 public:
  Lock (DebugNewTracer & DebugNewTracer) : _DebugNewTracer (DebugNewTracer) {
   _DebugNewTracer.lock ();
  }
  ~Lock () {
   _DebugNewTracer.unlock ();
  }
 private:
  DebugNewTracer & _DebugNewTracer;
 };
 typedef std::map<void*, Entry>::iterator iterator;
 friend class Lock;
 public:
 DebugNewTracer ();
 ~DebugNewTracer ();
 void Add (void* p, const char* file, int line);
 void Add (void* p, const char* file, int line, size_t size);
 void Remove (void* p);
 void Dump ();

 static bool Ready;

 private:
 void lock () { _lockCount++; }
 void unlock () { _lockCount--; }

 private:

 std::map<void*, Entry> _map;
 int _lockCount;
 size_t _totalsize;
#if defined(LOG_FILE)
 FILE* _logfp;
#endif
 };

 // The file that implements class DebugNewTracer
 // does NOT want the word "new" expanded.
 // The object DebugNewTrace is defined in that
 // implementation file but only declared in any other file.
#ifdef DEBUGNEW_CPP
DebugNewTracer DebugNewTrace;
#else
#define new new(__FILE__, __LINE__)
extern DebugNewTracer DebugNewTrace;
#endif

#endif//#ifndef __DEBUGNEW_H__


********************************************************
debugnew.cpp:

/*
 filename: debugnew.cpp

 This is used to substitute a version of the new operator that
 can be used for debugging memory leaks. In any (all?) code
 files #include debugnew.h. Add debugnew.cpp to the project.
*/

#include <iostream>
#include <map>

using namespace std;

 // This disables macro expansion of "new".
 // This statement should only appear in this file.
#define DEBUGNEW_CPP

#include "debugnew.h"

DebugNewTracer::DebugNewTracer () : _lockCount (0)
{
  // Once this object is constructed all calls to
  // new should be traced.

 Ready = true;
 _totalsize = 0;
#if defined(LOG_FILE)
 if( (_logfp=fopen(LOG_FILE_NAME,"wt")) == NULL )
 {
  printf(" Error! file: debugnew.log can not open@!\n");
  return;
 } 
 fprintf(_logfp,"new, delete list:\n");
 fflush(_logfp);
#endif
 
}
        
void DebugNewTracer::Add (void* p, const char* file, int line)
{
  // Tracing must not be done a second time
  // while it is already
  // in the middle of executing
 if (_lockCount > 0)
  return;

   // Tracing must be disabled while the map
   // is in use in case it calls new.
 DebugNewTracer::Lock lock (*this);
 _map [p] = Entry (file, line);
}

void DebugNewTracer::Add (void* p, const char* file, int line, size_t size)
{
 // Tracing must not be done a second time
 // while it is already
 // in the middle of executing
 if (_lockCount > 0)
  return;

  // Tracing must be disabled while the map
  // is in use in case it calls new.
 DebugNewTracer::Lock lock (*this);
#if 1
 //´Óȫ·¾¶ÖÐÌáÈ¡ÎļþÃû
 //linuxϵÄgcc,__FILE__½ö½ö°üÀ¨ÎļþÃû£¬windowsϵÄvc,__FILE__°üÀ¨È«Â·¾¶,ËùÒÔÓÐÕâÑùµÄ´¦Àí
 file = strrchr(file,'\\')== NULL?file:strrchr(file,'\\')+1;
#endif
 _map [p] = Entry (file, line, size);
 _totalsize += size;
#if defined(LOG_FILE)
 fprintf(_logfp,"*N p = 0x%08x, size = %6d,  %-22s, Line: %4d.  totalsize =%8d\n", p, size, file, line, _totalsize);
 fflush(_logfp);
#endif
}


void DebugNewTracer::Remove (void* p)
{
 // Tracing must not be done a second time
 // while it is already
 // in the middle of executing
 if (_lockCount > 0)
  return;

  // Tracing must be disabled while the map
  // is in use in case it calls new.
 DebugNewTracer::Lock lock (*this);

 iterator it = _map.find (p);

 if (it != _map.end ())
 {
  size_t size = (*it).second.Size();
  _totalsize -= size;
#if defined(LOG_FILE)
  fprintf(_logfp,"#D p = 0x%08x, size = %6u.%39stotalsize =%8d\n", p, size, "-----------------------------------  ", _totalsize );
  fflush(_logfp);
#endif
  _map.erase (it);
 }
 else
 {
#if defined(LOG_FILE)
  fprintf(_logfp,"#D p = 0x%08x. error point!!!\n", p );
  fflush(_logfp);
#endif
 }
}

DebugNewTracer::~DebugNewTracer ()
{
 // Trace must not be called if Dump indirectly
 // invokes new, so it must be disabled before
 // Dump is called. After this destructor executes
 // any other global objects that get destructed
 // should not do any tracing.
 Ready = false;
 Dump ();
#if defined(LOG_FILE)
 fclose(_logfp);
#endif
}

// If some global object is destructed after DebugNewTracer
// and if that object calls new, it should not trace that
// call to new.
void DebugNewTracer::Dump ()
{
 if (_map.size () != 0)
 {
  std::cout << _map.size () << " memory leaks detected\n";
#if defined(LOG_FILE)
  fprintf(_logfp, "\n\n***%d memory leaks detected\n", _map.size ());
  fflush(_logfp);
#endif
  for (iterator it = _map.begin (); it != _map.end (); ++it)
  {
   char const * file = it->second.File ();
   int line = it->second.Line ();
   int size = it->second.Size ();
   std::cout << file << ", "  << line << std::endl;
#if defined(LOG_FILE)
   fprintf(_logfp,"%s, %d, size=%d\n", file, line, size);
   fflush(_logfp);
#endif
  }
 }
 else
 {
  std::cout << "no leaks detected\n";
#if defined(LOG_FILE)
  fprintf(_logfp,"no leaks detected\n");
  fflush(_logfp);
#endif
 }

}

// If some global object is constructed before DebugNewTracer
// and if that object calls new, it should not trace that
// call to new.
bool DebugNewTracer::Ready = false;

void* operator new (size_t size, const char* file, int line)
{
 void * p = malloc (size);
 if (DebugNewTracer::Ready)
  DebugNewTrace.Add (p, file, line, size);
 return p;
}

void operator delete (void* p, const char* file, int line)
{
 file = 0; // avoid a warning about argument not used in function
 line = 0; // avoid a warning about argument not used in function
 
 if (DebugNewTracer::Ready)
  DebugNewTrace.Remove (p);
 free (p);
}
        
void* operator new [] (size_t size, const char*  file, int line)
{
 void * p = malloc (size);
 if (DebugNewTracer::Ready)
  DebugNewTrace.Add (p, file, line, size);
 return p;
}

void operator delete [] (void* p, const char* file, int line)
{
 file = 0; // avoid a warning about argument not used in function
 line = 0; // avoid a warning about argument not used in function
 
 if (DebugNewTracer::Ready)
   DebugNewTrace.Remove (p);
 free (p);
}
        
void* operator new (size_t size)
{
 void * p = malloc (size);
 // When uncommented these lines cause entries in the map for calls to new
 // that were not altered to the debugnew version. These are likely calls
 // in library functions and the presence in the dump of these entries
 // is usually misleading.
 // if (DebugNewTracer::Ready)
 //   DebugNewTrace.Add (p, "?", 0);
 return p;
}
 
void operator delete (void* p)
{
 if (DebugNewTracer::Ready)
  DebugNewTrace.Remove (p);
 free (p);
}

//add by yugang
void operator delete [] (void* p)
{
 if (DebugNewTracer::Ready)
  DebugNewTrace.Remove (p);
 free (p);
}



Sandy 2008-11-02 10:52 发表评论
]]>
虚承与虚基cȝ本质 http://www.shnenglu.com/SpringSnow/articles/64209.htmlSandySandyThu, 16 Oct 2008 14:19:00 GMThttp://www.shnenglu.com/SpringSnow/articles/64209.htmlhttp://www.shnenglu.com/SpringSnow/comments/64209.htmlhttp://www.shnenglu.com/SpringSnow/articles/64209.html#Feedback0http://www.shnenglu.com/SpringSnow/comments/commentRss/64209.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/64209.html    虚承和虚基cȝ定义是非常的单的Q同时也是非常容易判断一个承是否是虚?br>的,虽然q两个概늚定义是非常的单明的Q但是在C++语言中虚l承作ؓ一个比较生
ȝ但是又是l对必要的组成部份而存在着Qƈ且其行ؓ和模型均表现出和一般的l承体系
之间的巨大的差异Q包括访问性能上的差异Q,现在我们来d的从语言、模型、性能?br>应用{多个方面对虚承和虚基c进行研I?br>    首先q是先给l承和虚基类的定义?br>    虚承:在承定义中包含了virtual关键字的l承关系Q?br>    虚基c:在虚l承体系中的通过virtuall承而来的基c,需要注意的是:
            struct CSubClass : public virtual CBase {}; 其中CBaseUC为CSubClass
            的虚基类Q而不是说CBase是个虚基类Q因为CBaseq可以不不是虚承体p?br>            中的基类?br>    有了上面的定义后Q就可以开始虚l承和虚基类的本质研I了Q下面按照语法、语义?br>模型、性能和应用五个方面进行全面的描述?br>
    1. 语法
       语法有语a的本w的定义所军_QM上来说非常的单,如下Q?br>           struct CSubClass : public virtual CBaseClass {};
       其中可以采用public、protected、private三种不同的承关键字q行修饰Q只?br>       保包含virtual可以了Q这样一来就形成了虚l承体系Q同时CBaseClass成?br>       了CSubClass的虚基类了?br>       其实q没有那么的单,如果出现虚承体pȝq一步承会出现什么样的状况呢Q?br>       如下所C:
            /*
             * 带有数据成员的基c?br>             */
            struct CBaseClass1
            {
                CBaseClass1( size_t i ) : m_val( i ) {}
            
                size_t m_val;
            };
            /*
             * 虚拟l承体系
             */
            struct CSubClassV1 : public virtual CBaseClass1
            {
                CSubClassV1( size_t i ) : CBaseClass1( i ) {}
            };           
            struct CSubClassV2 : public virtual CBaseClass1
            {
                CSubClassV2( size_t i ) : CBaseClass1( i ) {}
            };           
            struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
            {
                CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
            };           
            struct CDiamondSubClass1 : public CDiamondClass1
            {
                CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
            };
       注意上面代码中的CDiamondClass1和CDiamondSubClass1两个cȝ构造函数初始化?br>       表中的内宏V可以发现其中均包含了虚基类CBaseClass1的初始化工作Q如果没有这
       个初始化语句׃D~译旉误,Z么会q样呢?一般情况下不是只要?br>       CSubClassV1和CSubClassV2中包含初始化可以了么?要解释该问题必须要明白虚
       l承的语义特征,所以参看下面语义部分的解释?br>      
    2. 语义
       从语义上来讲什么是虚承和虚基cdQ上面仅仅是从如何在C++语言中书写合法的
       虚承类定义而已。首先来了解一下virtualq个关键字在C++中的公共含义Q在C++
       语言中仅仅有两个地方可以使用virtualq个关键字,一个就是类成员虚函数和q里
       所讨论的虚l承。不要看q两U应用场合好像没什么关p,其实他们在背景语义上
       hvirtualq个词所代表的共同的含义Q所以才会在q两U场合用相同的关键字?br>       那么virtualq个词的含义是什么呢Q?br>       virtual在《美国传l词典[双解]》中是这样定义的Q?br>           adj.QŞ容词Q?br>           1. Existing or resulting in essence or effect though not in actual
              fact, form, or name:
              实质上的Q实际上的:虽然没有实际的事实、Ş式或名义Q但在实际上或效
              果上存在或生的Q?br>           2. Existing in the mind, especially as a product of the imagination.
              Used in literary criticism of text.
              虚的Q内心的Q在头脑中存在的Q尤指意想的产物。用于文学批评中?br>       我们采用W一个定义,也就是说被virtual所修饰的事物或现象在本质上是存在的Q?br>       但是没有直观的Ş式表玎ͼ无法直接描述或定义,需要通过其他的间接方式或手段
       才能够体现出其实际上的效果?br>       那么在C++中就是采用了q个词意Q不可以在语a模型中直接调用或体现的,但是?br>       实是存在可以被间接的方式q行调用或体现的。比如:虚函数必要通过一U间接的
       q行Ӟ而不是编译时Q机制才能够Ȁz(调用Q的函数Q而虚l承也是必须在运?br>       时才能够q行定位讉K的一U体制。存在,但间接。其中关键就在于存在、间接和?br>       享这三种特征?br>       对于虚函数而言Q这三个特征是很好理解的Q间接性表明了他必dq行时根据实?br>       的对象来完成函数dQ共享性表象在基类会共享被子类重蝲后的虚函敎ͼ其实指向
       相同的函数入口?br>       对于虚承而言Q这三个特征如何理解呢?存在卌Cl承体系和虚基类实存在Q?br>       间接性表明了在访问虚基类的成员时同样也必通过某种间接机制来完成(下面模型
       中会讲到Q,׃n性表象在虚基cM在虚l承体系中被׃nQ而不会出现多份拷贝?br>       那现在可以解释语法小节中留下来的那个问题了,“Z么一旦出C虚基c,必
       d没有一个承类中都必须包含虚基cȝ初始化语?#8221;。由上面的分析可以知道,
       虚基cL被共享的Q也是在承体pM无论被承多次Q对象内存模型中均只?br>       出现一个虚基类的子对象Q这和多l承是完全不同的Q,q样一来既然是׃n的那?br>       每一个子c都不会独占Q但是总还是必要有一个类来完成基cȝ初始化过E(因ؓ
       所有的对象都必被初始化,哪怕是默认的)Q同时还不能够重复进行初始化Q那?br>       底谁应该负责完成初始化呢QC++标准中(也是很自然的Q选择在每一ơ承子cM
       都必M写初始化语句Q因为每一ơ承子cd能都会用来定义对象)Q而在最下层
       l承子类中实际执行初始化q程。所以上面在每一个承类中都要书写初始化语句Q?br>       但是在创建对象时Q而仅仅会在创建对象用的类构造函C实际的执行初始化语句Q?br>       其他的初始化语句都会被压制不调用?br>      
    3. 模型
       Z实现上面所说的三种语义含义Q在考虑对象的实现模型(也就是内存模型)时就
       很自然了。在C++中对象实际上是一个连l的地址I间的语义代表,我们来分析虚
       l承下的内存模型?br>       3.1. 存在
           也就是说在对象内存中必须要包含虚基类的完整子对象Q以便能够完成通过地址
           完成对象的标识。那么至于虚基类的子对象会存攑֜对象的那个位|(头、中间?br>           NQ则由各个编译器选择Q没有差别。(在VC8中无基类被声明在什么位|,
           虚基cȝ子对象都会被攄在对象内存的NQ?br>       3.2. 间接
           间接性表明了在直接虚基承子类中一定包含了某种指针Q偏UL表格Q来完成?br>           q子c访问虚基类子对象(或成员)的间接手D(因ؓ虚基cd对象是共享的Q?br>           没有定关系Q,至于采用何种手段q译器选择。(在VC8中在子类中放|了
           一个虚基类指针vbcQ该指针指向虚函数表中的一个slotQ该slot中存攑ֈ虚基
           cd对象的偏U量的负|实际上就是个以补码表C的intcd的|在计虚
           基类子对象首地址Ӟ需要将该偏U量取绝对值相加,q个主要是ؓ了和虚表
           中只能存放虚函数地址q一要求相区别,因ؓ地址是原码表C的无符号intcd
           的|
       3.3. ׃n
           ׃n表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象Qƈ且通过
           某种间接的机制来完成׃n的引用关pR在介绍完整个内容后会附上测试代码,
           体现q些内容?br>    4. 性能
       ׃有了间接性和׃n性两个特征,所以决定了虚承体pM的对象在讉K时必?br>       会在旉和空间上与一般情冉|较大不同?br>       4.1. 旉
           在通过l承cd象访问虚基类对象中的成员Q包括数据成员和函数成员Q时Q都
           必须通过某种间接引用来完成,q样会增加引用寻址旉Q就和虚函数一PQ?br>           其实是调整this指针以指向虚基类对象Q只不过q个调整是运行时间接完成的?br>           Q在VC8中通过打开汇编输出Q可以查?.cod文g中的内容Q在讉K虚基cd?br>           成员时会形成三条mov间接d语句Q而在讉K一般承类对象时仅仅只有一条mov
           帔R直接d语句Q?br>       4.2. I间
           ׃׃n所以不同在对象内存中保存多份虚基类子对象的拯Q这栯之多l承
           节省I间?br>    5. 应用
       谈了那么多语aҎ和内容Q那么在什么情况下需要用虚l承Q而一般应该如何
       用呢Q?br>       q个问题其实很难有答案,一般情况下如果你确性出现多l承没有必要Q必要׃n
       基类子对象的时候可以考虑采用虚承关p(C++标准ios体系是q样的)。由于每
       一个承类都必d含初始化语句而又仅仅只在最底层子类中调用,q样可能׃?br>       得某些上层子cd到的虚基cd对象的状态不是自己所期望的(因ؓ自己的初始化?br>       句被压制了)Q所以一般徏议不要在虚基cM包含M数据成员Q不要有状态)Q只
       可以作ؓ接口cL提供?br>
附录Q测试代?br>#include <ctime>
#include <iostream>

/*
 * 带有数据成员的基c?br> */
struct CBaseClass1
{
    CBaseClass1( size_t i ) : m_val( i ) {}

    size_t m_val;
};
/*
 * 虚拟l承体系
 */
struct CSubClassV1 : public virtual CBaseClass1
{
    CSubClassV1( size_t i ) : CBaseClass1( i ) {}
};

struct CSubClassV2 : public virtual CBaseClass1
{
    CSubClassV2( size_t i ) : CBaseClass1( i ) {}
};

struct CDiamondClass1 : public CSubClassV1, public CSubClassV2
{
    CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
};

struct CDiamondSubClass1 : public CDiamondClass1
{
    CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
};
/*
 * 正常l承体系
 */
struct CSubClassN1 : public CBaseClass1
{
    CSubClassN1( size_t i ) : CBaseClass1( i ) {}
};
struct CSubClassN2 : public CBaseClass1
{
    CSubClassN2( size_t i ) : CBaseClass1( i ) {}
};
struct CMultiClass1 : public CSubClassN1, public CSubClassN2
{
    CMultiClass1( size_t i ) : CSubClassN1( i ), CSubClassN2( i ) {}
};
struct CMultiSubClass1 : public CMultiClass1
{
    CMultiSubClass1( size_t i ) : CMultiClass1( i ) {}
};
/*
 * 不带有数据成员的接口基类
 */
struct CBaseClass2
{
    virtual void func() {};
    virtual ~CBaseClass2() {}
};
/*
 * 虚拟l承体系
 */
// struct CBaseClassX { CBaseClassX() {i1 = i2 = 0xFFFFFFFF;} size_t i1, i2;};
struct CSubClassV3 : public virtual CBaseClass2
{
};
struct CSubClassV4 : public virtual CBaseClass2
{
};
struct CDiamondClass2 : public CSubClassV3, public CSubClassV4
{
};
struct CDiamondSubClass2 : public CDiamondClass2
{
};

/*
 * 正常l承体系
 */
struct CSubClassN3 : public CBaseClass2
{
};
struct CSubClassN4 : public CBaseClass2
{
};
struct CMultiClass2 : public CSubClassN3, public CSubClassN4
{
};
struct CMultiSubClass2 : public CMultiClass2
{
};

/*
 * 内存布局用类声明.
 */
struct CLayoutBase1
{
    CLayoutBase1() : m_val1( 0 ), m_val2( 1 ) {}

    size_t m_val1, m_val2;
};
struct CLayoutBase2
{
    CLayoutBase2() : m_val1( 3 ) {}

    size_t m_val1;
};
struct CLayoutSubClass1 : public virtual CBaseClass1, public CLayoutBase1, public CLayoutBase2
{
    CLayoutSubClass1() : CBaseClass1( 2 ) {}
};


#define MAX_TEST_COUNT 1000 * 1000 * 16
#define TIME_ELAPSE() ( std::clock() - start * 1.0 ) / CLOCKS_PER_SEC

int main( int argc, char *argv[] )
{
    /*
     * cMpM的尺?
     */
    std::cout << "================================ sizeof ================================" << std::endl;
    std::cout << "    ----------------------------------------------------------------" << std::endl;
    std::cout << "sizeof( CBaseClass1 )       = " << sizeof( CBaseClass1 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassV1 )       = " << sizeof( CSubClassV1 ) << std::endl;
    std::cout << "sizeof( CSubClassV2 )       = " << sizeof( CSubClassV2 ) << std::endl;
    std::cout << "sizeof( CDiamondClass1 )    = " << sizeof( CDiamondClass1 ) << std::endl;
    std::cout << "sizeof( CDiamondSubClass1 ) = " << sizeof( CDiamondSubClass1 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassN1 )       = " << sizeof( CSubClassN1 ) << std::endl;
    std::cout << "sizeof( CSubClassN2 )       = " << sizeof( CSubClassN2 ) << std::endl;
    std::cout << "sizeof( CMultiClass1 )      = " << sizeof( CMultiClass1 ) << std::endl;
    std::cout << "sizeof( CMultiSubClass1 )   = " << sizeof( CMultiSubClass1 ) << std::endl;

    std::cout << "    ----------------------------------------------------------------" << std::endl;
    std::cout << "sizeof( CBaseClass2 )       = " << sizeof( CBaseClass2 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassV3 )       = " << sizeof( CSubClassV3 ) << std::endl;
    std::cout << "sizeof( CSubClassV4 )       = " << sizeof( CSubClassV4 ) << std::endl;
    std::cout << "sizeof( CDiamondClass2 )    = " << sizeof( CDiamondClass2 ) << std::endl;
    std::cout << "sizeof( CDiamondSubClass2 ) = " << sizeof( CDiamondSubClass2 ) << std::endl;
    std::cout << std::endl;
    std::cout << "sizeof( CSubClassN3 )       = " << sizeof( CSubClassN3 ) << std::endl;
    std::cout << "sizeof( CSubClassN4 )       = " << sizeof( CSubClassN4 ) << std::endl;
    std::cout << "sizeof( CMultiClass2 )      = " << sizeof( CMultiClass2 ) << std::endl;
    std::cout << "sizeof( CMultiSubClass2 )   = " << sizeof( CMultiSubClass2 ) << std::endl;
    /*
     * 对象内存布局
     */
    std::cout << "================================ layout ================================" << std::endl;
    std::cout << "    --------------------------------MI------------------------------" << std::endl;
    CLayoutSubClass1 *lsc = new CLayoutSubClass1;
    std::cout << "sizeof( CLayoutSubClass1 )   = " << sizeof( CLayoutSubClass1 ) << std::endl;
    std::cout << "CLayoutBase1 offset of CLayoutSubClass1 is " << (char*)(CLayoutBase1 *)lsc - (char*)lsc << std::endl;
    std::cout << "CBaseClass1  offset of CLayoutSubClass1 is " << (char*)(CBaseClass1  *)lsc - (char*)lsc << std::endl;
    std::cout << "CLayoutBase2 offset of CLayoutSubClass1 is " << (char*)(CLayoutBase2 *)lsc - (char*)lsc << std::endl;

    int *ptr = (int*)lsc;
    std::cout << "vbc in CLayoutSubClass1 is " << *(int*)ptr[3] << std::endl;

    delete lsc;

    std::cout << "    --------------------------------SI------------------------------" << std::endl;
    CSubClassV1 *scv1 = new CSubClassV1( 1 );
    std::cout << "sizeof( CSubClassV1 )   = " << sizeof( CSubClassV1 ) << std::endl;
    std::cout << "CBaseClass1 offset of CSubClassV1 is " << (char*)(CBaseClass1 *)scv1 - (char*)scv1 << std::endl;

    ptr = (int*)scv1;
    std::cout << "vbc in CSubClassV1 is " << *(int*)ptr[0] << std::endl;

    delete scv1;

    /*
     * 性能试
     */
    std::cout << "================================ Performance ================================" << std::endl;
    double times[4];
    size_t idx = 0;

    CSubClassV1 *ptr1 = new CDiamondClass1( 1 );
    std::clock_t start = std::clock();
    {
        for ( size_t i = 0; i < MAX_TEST_COUNT; ++i )
            ptr1->m_val = i;
    }
    times[idx++] = TIME_ELAPSE();
    delete static_cast<CDiamondClass1*>( ptr1 );

    CSubClassN1 *ptr2 = new CMultiClass1( 0 );
    start = std::clock();
    {
        for ( size_t i = 0; i < MAX_TEST_COUNT; ++i )
            ptr2->m_val = i;
    }
    times[idx++] = TIME_ELAPSE();
    delete static_cast<CMultiClass1*>( ptr2 );

    std::cout << "CSubClassV1::ptr1->m_val " << times[0] << " s" << std::endl;
    std::cout << "CSubClassN1::ptr2->m_val " << times[1] << " s" << std::endl;

    return 0;
}

试环境Q?br>    软g环境QVisual Studio2005 Pro + SP1, boost1.34.0
    g环境QPentiumD 3.0GHz, 4G RAM
试数据Q?br>================================ sizeof ================================
    ----------------------------------------------------------------
sizeof( CBaseClass1 )       = 4

sizeof( CSubClassV1 )       = 8
sizeof( CSubClassV2 )       = 8
sizeof( CDiamondClass1 )    = 12
sizeof( CDiamondSubClass1 ) = 12

sizeof( CSubClassN1 )       = 4
sizeof( CSubClassN2 )       = 4
sizeof( CMultiClass1 )      = 8
sizeof( CMultiSubClass1 )   = 8
    ----------------------------------------------------------------
sizeof( CBaseClass2 )       = 4

sizeof( CSubClassV3 )       = 8
sizeof( CSubClassV4 )       = 8
sizeof( CDiamondClass2 )    = 12
sizeof( CDiamondSubClass2 ) = 12

sizeof( CSubClassN3 )       = 4
sizeof( CSubClassN4 )       = 4
sizeof( CMultiClass2 )      = 8
sizeof( CMultiSubClass2 )   = 8
================================ layout ================================
    --------------------------------MI------------------------------
sizeof( CLayoutSubClass1 )   = 20
CLayoutBase1 offset of CLayoutSubClass1 is 0
CBaseClass1  offset of CLayoutSubClass1 is 16
CLayoutBase2 offset of CLayoutSubClass1 is 8
vbc in CLayoutSubClass1 is -12
    --------------------------------SI------------------------------
sizeof( CSubClassV1 )   = 8
CBaseClass1 offset of CSubClassV1 is 4
vbc in CSubClassV1 is 0
================================ Performance ================================
CSubClassV1::ptr1->m_val 0.062 s
CSubClassN1::ptr2->m_val 0.016 s

l果分析Q?br>    1. ׃虚承引入的间接性指针所以导致了虚承类的尺怼增加4个字节;
    2. 由Layout输出可以看出Q虚基类子对象被攑֜了对象的NQ偏UMؓ16Q,q且vbc
       指针必须紧紧的接在虚基类子对象的前面Q所以vbc指针所指向的内容ؓ“偏移 - 4”Q?br>    3. ׃VC8偏UL在了虚函数表中,所以ؓ了区分函数地址和偏U,所以偏UL用补
       码int表示的负|
    4. 间接性可以通过性能来看出,在虚l承体系同通过指针讉K成员时的旉一般是一?br>       c访问情况下?倍左叻IW合汇编语言输出文g中的汇编语句的安排?

Sandy 2008-10-16 22:19 发表评论
]]>
虚拟l承入门http://www.shnenglu.com/SpringSnow/articles/64206.htmlSandySandyThu, 16 Oct 2008 13:48:00 GMThttp://www.shnenglu.com/SpringSnow/articles/64206.htmlhttp://www.shnenglu.com/SpringSnow/comments/64206.htmlhttp://www.shnenglu.com/SpringSnow/articles/64206.html#Feedback1http://www.shnenglu.com/SpringSnow/comments/commentRss/64206.htmlhttp://www.shnenglu.com/SpringSnow/services/trackbacks/64206.html链接Q?a href="http://www.shnenglu.com/franksunny/archive/2008/10/16/64168.html">
http://www.shnenglu.com/franksunny/archive/2008/10/16/64168.html

原文Q?br>

Z么要引入虚拟l承Q?/span>

虚拟l承在一般的应用中很用刎ͼ所以也往往被忽视,q也主要是因为在C++中,多重l承是不推荐的,也ƈ不常用,而一旦离开了多重承,虚拟l承完全失M存在的必要(因ؓq样只会降低效率和占用更多的I间Q关于这一点,我自p没有太多深刻的理解,有兴的可以看网l上白杨的作?/span>?/span>RTTI、虚函数和虚?span>cȝ开销分析及用指?span>?/span>Q说实话我目前还没看得很明白Q高人可以指点下我)?/span>

以下面的一个例子ؓ例:

#include <iostream.h>

#include <memory.h>

class CA

{

    int k; //如果基类没有数据成员Q则在这里多重承编译不会出C义?/span>

public:

    void f() {cout << "CA::f" << endl;}

};

 

class CB : public CA

{

};

 

class CC : public CA

{

};

 

class CD : public CB, public CC

{

};

 

void main()

{

    CD d;

    d.f();

}

当编译上qC码时Q我们会收到如下的错误提C:

error C2385: 'CD::f' is ambiguous

即编译器无法定你在d.f()中要调用的函?/span>f到底是哪一个。这里可能会让h觉得有些奇怪,命名只定义了一?/span>CA::fQ既然大安z?/span>CAQ那自然是调用?/span>CA::fQؓ什么还无法定呢?

q是因ؓ~译器在q行~译的时候,需要确定子cȝ函数定义Q如CA::f是确定的Q那么在~译CB?/span>CC时还需要在~译器的语法树中生成CB::fQ?/span>CC::f{标识,那么Q在~译CD的时候,׃CB?/span>CC都有一个函?/span>fQ此Ӟ~译器将试图生成q两?/span>CD::f标识Q显然这时就要报错了。(当我们不使用CD::f的时候,以上标识都不会生成,所以,如果Ld.f()一句,E序顺利通过~译Q?/span>

 

要解册个问题,有两个方法:

1、重载函?/span>f()Q此时由于我们明定义了CD::fQ编译器查到CD::f()调用时就无需再像上面一样去逐生成CD::f标识了;

此时CD的元素结构如下:

|CB(CA)|

|CC(CA)|

故此时的sizeof(CD) = 8;Q?/span>CB?/span>CC各有一个元?/span>kQ?/span>

2、用虚拟承:虚拟l承又称作共享承,q种׃n其实也是~译期间实现的,当用虚拟承时Q上面的E序变成下面的形式Q?/span>

#include <iostream.h>

#include <memory.h>

class CA

{

    int k;

public:

    void f() {cout << "CA::f" << endl;}

};

 

class CB : virtual public CA //也有一U写法是class CB : public virtual CA

{                       //实际上这两种Ҏ都可?/span>

};

 

class CC : virtual public CA

{

};

 

class CD : public CB, public CC

{

};

 

void main()

{

    CD d;

    d.f();

}

此时Q当~译器确?/span>d.f()调用的具体含义时Q将生成如下?/span>CDl构Q?/span>

|CB|

|CC|

|CA|

同时Q在CB?/span>CC中都分别包含了一个指?/span>CA的虚基类指针列表vbptrQ?/span>virtual base table pointerQ,其中记录的是?/span>CB?/span>CC的元素到CA的元素之间的偏移量。此Ӟ不会生成各子cȝ函数f标识Q除非子c重载了该函敎ͼ从而达?#8220;׃n”的目的(q里的具体内存布局Q可以参看钻矛_l承内存布局Q在白杨的那文章中也有Q?/span>

也正因此Q此时的sizeof(CD) = 12Q两?/span>vbptr + sizoef(int)Q?/span>;

 

另注Q?/span>

如果CBQ?/span>CC中各定义一?/span>int型变量,?/span>sizeof(CD)变?/span>20(两个vbptr + 3?/span>sizoef(int)

如果CA中添加一?/span>virtual void f1(){}Q?/span>sizeof(CD) = 16Q两?/span>vbptr + sizoef(int)+vptrQ?/span>;

再添?/span>virtual void f2(){}Q?/span>sizeof(CD) = 16不变。原因如下所C:带有虚函数的c,其内存布局上包含一个指向虚函数列表的指针(vptrQ,q跟有几个虚函数无关?/span>

以上内容涉及到类对象内存布局问题Q本难以做过多展开Q先贴这么多Q本文章只是考虑对于虚拟l承q行入门Q至于效率、应用等未作展开。本文在|上文章基础上修改了下而得此篇Q原文蝲?/span>http://blog.csdn.net/billdavid/archive/2004/06/23/24317.aspx

另外关于虚承和虚基cȝ讨论Q博客园有篇文章?/span>虚承与虚基cȝ本质》,ȝ得更l一炏V?/span>



Sandy 2008-10-16 21:48 发表评论
]]>
ŮþþŮ| ھƷþþþþþӰ鶹| þ¶ݺɫ| þþþavۺϲҰ| þþþùƷ| ޾ƷþþþþͼƬ | 97þþƷһ| þۺϹapp| 69ۺϾþþƷ| ۿþ| þþþþۺ| ԴӰȷþԴ| þþWWWëƬ| ڸþþþþ| AAAþþþƷ| þþҹƷ| 97þۺϾƷþþۺ| ĻӰӾþþѹۿ| ƷþóӰԺ| ˾Ʒþþþ7777| ȾþùƷ| һɫþHEZYO| þþƷһ| AVþþþò| þòӰ| þùƷHDAV| һɫþ88ۺպƷ| 99ƷþþƷ| ޾ƷþþþþĻ| Ʒۺþþþþ | 69Ʒþþþ9999APGF | þþƷ91þ鶹 | þþþþþ99Ʒѹۿ| ˾þ뾫ƷĻ| ձƬҹþ| ޾þþһ| þþþAVרJN| þ˿ྫƷĻ| 91þþþþþ| þ99þ99СݾƷӿ| þAVӰ|