??xml version="1.0" encoding="utf-8" standalone="yes"?> 位运符有:&(按位?、|(按位?、^(按位异或)、~ (按位取反)? 位运符的一些简单应? 按位或运的典型用法是将一个位串信息的某几位置?。如要获得最??Q其他位与变量j的其他位相同Q可用逻辑或运?17|j 按位异或q算的典型用法是求一个位串信息的某几位信息的反。如Ʋ求整型变量j的最?位信息的反,用逻辑异或q算017^jQ就能求得j最?位的信息的反,卛_来ؓ1的位Q结果是0,原来?的位Q结果是1?交换两个|不用临时变量,假如a=3,b=4。想a和b的g换,可以用以下赋D句实玎ͼ 取反q算常用来生成与pȝ实现无关的常数。如要将变量x最?位置?Q其余位不变Q可用代码x = x & ~077实现。以上代码与整数x?个字节还是用4个字节实现无兟? 位运用来对位串信息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法: UMq算与位q算l合能实现许多与位串q算有关的复杂计。设变量的位自右臛_序~号Q自0位至15位,有关指定位的表达式是不超q?5的正整数。以下各代码分别有它们右Ҏ释所C的意义Q?br> (1) 判断int型变量a是奇数还是偶?其是对大数的判?br> a&1 == 0 偶数
摘自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>
a=a^b; b=b^a; a=a^b;
当两个长度不同的数据q行位运时(例如long型数据与int型数?Q将两个q算分量的右端对齐进行位q算。如果短的数为正敎ͼ高位?补满Q如果短的数敎ͼ高位?补满。如果短的ؓ无符h敎ͼ则高位L?补满?/p>
printf("%d\n\n\n", -2>>4);
若输出结果ؓ-1Q是采用术右移Q输出结果ؓ一个大整数Q则为逻辑右移?/p>
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未考证
]]>
陈皓
前言
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
陈皓
重复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
陈皓
前言
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
记得Q有位微软的C++楚人物QHerb Sutterl这U技巧称为Pimpl ( Private Implemention ), 用的恰到好处Ӟ可以提高目开发速度Q同时模块的头文仉?include关系得以~解Q可以避开循环依赖Q而且可以获得一个良好的物理设计?br>
ȝ:
PimplҎ感觉很不?
使用q个Ҏ的时?一定要注意的是在这个地方的变化,q个是我W二遍看的时候才注意到的.
class SoundSystem {
public:
// ...
private:
StreamFilter currentFilter;
EmitModeConfig modeConfig;
};
采用PimplҎ?变ؓ
class SoundSystem {
public:
// ...
private:
StreamFilter* currentFilterPtr;
EmitModeConfig* modeConfigPtr;
};
所以在.cpp文g中就有了new和delete的操?
对于q种Ҏ有一个疑?对于那种存在包含众多cȝ情况?q种Ҏ的驾驭不是一般h能够掌握的吧.或许q种Ҏ׃太用了,不如{待一?~译.
// 创徏互斥?br>HANDLE m_hMutex = CreateMutex(NULL, FALSE, "Sample07"); // 查错误代?br>if (GetLastError() == ERROR_ALREADY_EXISTS) { // 如果已有互斥量存在则释放句柄q复位互斥量 CloseHandle(m_hMutex); m_hMutex = NULL; // E序退?br> return FALSE; } |
HANDLE CreateMutex( LPSECURITY_ATTRIBUTES lpMutexAttributes, // 指向安全属性的指针 BOOL bInitialOwner, // 初始化互斥对象的所有?br> LPCTSTR lpName // 指向互斥对象名的指针 ); |
使用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>
其中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>
不允怋用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++~译器下却不同,具体表现为:
看来两个~译器对q一问题的看法不太一致?/p>
问题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>
q里有三个问题需要解冻I
__FILE__和__LINE__应该被自动处理,而非每次作ؓ参数传递给errorQ这样会让error的用者感觉更爽些Q它的Ş式可能是下面q样Q?br>
我希望上面的宏AT展开为:"c:\temp\test.cpp:5"。而新的error函数则变成:
因ؓBorland C++ Builder~译器能够自动合q相ȝ字符Ԍ因此我把AT写成下面q样Q?br>
然而它却Ş工了Q因为__LINE__被扩展成了整数而非字符Ԍ所以宏展开后变成:
"c:\temp\test.cpp" ":" 5
q是一个无效的字符Ԍ因ؓ末尾有一个未被双引号包含的整数?
怎么办?别着急,一个特D的预编译指C器“#”能够帮我们将一个变量{换成字符丌Ӏ所以重新定义宏Q?
#define AT __FILE__ ":" #__LINE__
嘿嘿Q这h行了吧。别高兴得太早,q样也是不行的。因为编译器会抱?是个无效字符。其实,问题?预编译指C器只有q样使用才会
被正识别:
#define symbol(X) #X
解决Ҏ是再用一个宏来包装STRINGIFY()Q?br>
http://www.shnenglu.com/iuranus/archive/2008/12/25/70315.html
具体归纳如下:
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>
标签Q?无标{?/p>
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>); |
//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); |
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 }; |
CWinThread *AfxBeginThread( //从CWinThreadz的类?RUNTIME_CLASS CRuntimeClass *pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL ); |
![]() |
///////////////////////////////////////////////////////////////////////////// // 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() |
CMyUIThread *pThread; pThread = (CMyUIThread*) AfxBeginThread( RUNTIME_CLASS(CMyUIThread) ); |
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>
![]() |
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; }; |
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 } } |
int array1[10], array2[10]; CMutexSection section; //创徏一个CMutexcȝ对象 //赋值线E控制函?br>UINT EvaluateThread(LPVOID param) { CSingleLock singlelock; singlelock(§ion); //互斥区域 singlelock.Lock(); for (int i = 0; i < 10; i++) array1[i] = i; singlelock.Unlock(); } //拯U程控制函数 UINT CopyThread(LPVOID param) { CSingleLock singlelock; singlelock(§ion); //互斥区域 singlelock.Lock(); for (int i = 0; i < 10; i++) array2[i] = array1[i]; singlelock.Unlock(); } } AfxBeginThread(EvaluateThread, NULL); //启动赋值线E?br>AfxBeginThread(CopyThread, NULL); //启动拯U程 |
for (int i = 0; i < 10; i++) array1[i] = i; |
for (int i = 0; i < 10; i++) array2[i] = array1[i]; |
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 ); |
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 ); |
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() }; |
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; } |
#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() }; |
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; } |
// 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 } |
![]() |
我们提取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() }; |
virtual BOOL InitApplication(); virtual BOOL InitInstance(); virtual int Run(); |
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow) { // call shared/exported WinMain return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow); } |
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; } |
CWinThread* pThread = AfxGetThread(); CWinApp* pApp = AfxGetApp(); AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow) pApp->InitApplication() pThread->InitInstance() pThread->Run(); |
// 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 } |
///////////////////////////////////////////////////////////////////////////// // 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; } |
do while (::PeekMessage(...)); |
![]() ![]() ![]() |
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() |
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() |
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 } |
m_hThread = ::GetCurrentThread();//theApp的线E句柄等于当前线E句? m_nThreadID = ::GetCurrentThreadId();//theApp的线EID{于当前U程ID |
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) |
非常单!
![]() ![]() |
![]()
|
在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>
![]() ![]() |
![]()
|
和其它一些内存泄漏检的方式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>
![]() ![]() |
![]()
|
上述Ҏ最初工作得相当好,直到我开始创建大量的对象为止。由于每ơ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>
![]() ![]() |
![]()
|
我们看一下以下的单程序示例:
#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个问题了?
![]() ![]() |
![]()
|
IBM developerWorks上刊载了z琨先生设计实现的一个Linux上的内存泄漏方法([z琨2003]Q。我的方案与其相比,主要区别如下Q?/p>
优点Q?
~点Q?
另外Q现在已存在不少商业和Open Source的内存泄漏检器Q本文不打算一一再做比较。Debug_new与它们相比,功能上ȝ来说仍较弱,但是Q其良好的易用性和跨^台性、低廉的附加开销q是h很大优势的?/p>
![]() ![]() |
![]()
|
以上D落基本上已l说明了debug_new的主要特炏V下面做一个小的ȝ?/p>
重蝲的算W:
提供的函敎ͼ
提供的全局变量
可重定义的宏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 中国|站?
内存泄露的工具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);
}
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>