一个对非静态成员函数及指针的试验(请高手分析原理)

huche 2004-04-19 01:15:55
#include "iostream.h"

class CTest
{
public:
CTest(){m_nTest = 5;};
~CTest(){};
void Func(void)
{ cout << "Func of class CTest" << endl;
cout << m_nTest << endl;
}
private:
int m_nTest;
};

void Test(void)
{
CTest *p;
{
CTest test;
p = &test;
}
p->Func(); // 能够正常输出,内存竟然没有释放?
}

void Test2(CTest **p)
{
CTest test;
*p = &test;
}

void main()
{
Test();

CTest *p;
Test2(&p);
p->Func(); // 函数能够正常执行,只是m_Test输出不对,说明m_nTest被释放

CTest *p2;
p2->Func(); // 函数能够被调用,只是访问m_nTest报访问内存错误.
}

// 在我机器上,程序执行结果为
// Func of class CTest
// 5
// Func of class CTest
// 4198688
// Func of class CTest
// "弹内存访问错误框"

// 上述表明调用类的成员函数并不需要初始化该类的对象,条件是不需要访问该类的成员变量。
// void CTest::Func(void) 被编译为 void Func(const CTest *this);

// 但是其他的我就无法解释了,请大家帮忙分析分析。
...全文
44 点赞 收藏 10
写回复
10 条回复
切换为时间正序
请发表友善的回复…
发表回复
freefalcon 2004-04-19
楼上几位从上层分析得差不多了,我从下层谈谈个人看法

首先,成员函数在底层看来与全局函数没有区别,它们都位于代码区,只是在上层访问权限不同
其次,一个对象在其生命期结束后,其所占的内存就无效了,至于其中的数据是否会立即改变,那是不可预料的,只能说它会在将来的某个时候被系统或者其它程序利用

下面结合汇编代码分析一下

1. Test()函数的汇编代码
715: void Test(void)
716: {
004017C0 push ebp
004017C1 mov ebp,esp
004017C3 sub esp,48h
004017C6 push ebx
004017C7 push esi
004017C8 push edi
004017C9 lea edi,[ebp-48h]
004017CC mov ecx,12h
004017D1 mov eax,0CCCCCCCCh
004017D6 rep stos dword ptr [edi]
717: CTest *p;
718: {
719: CTest test;
004017D8 lea ecx,[test]
004017DB call @ILT+705(CTest::CTest) (004012c6)
720: p = &test; //<<<<<<<<<<< p在我机器上的值为0x0012ff1c,即test的地址为0x0012ff1c
004017E0 lea eax,[test]
004017E3 mov dword ptr [ebp-4],eax
721: }
004017E6 lea ecx,[test]
004017E9 call @ILT+5(CTest::~CTest) (0040100a) //test在此“挂掉”,调用了析构函数,但0x0012ff1c中的数据并没有改变
722: p->Func(); // 能够正常输出,内存竟然没有释放?
004017EE mov ecx,dword ptr [ebp-4]
004017F1 call @ILT+580(CTest::Func) (00401249) // 所以正常输出
723: }

2. Test2()的汇编代码
725: void Test2(CTest **p)
726: {
00401970 push ebp
00401971 mov ebp,esp
00401973 sub esp,44h
00401976 push ebx
00401977 push esi
00401978 push edi
00401979 lea edi,[ebp-44h]
0040197C mov ecx,11h
00401981 mov eax,0CCCCCCCCh
00401986 rep stos dword ptr [edi]
727: CTest test;
00401988 lea ecx,[ebp-4]
0040198B call @ILT+705(CTest::CTest) (004012c6)
728: *p = &test; //<<<<<<<<<<< p的值仍然为0x0012ff1c,说明前面Test()中分配的内存至少在这里已经被系统重新利用了
00401990 mov eax,dword ptr [ebp+8]
00401993 lea ecx,[ebp-4]
00401996 mov dword ptr [eax],ecx
729: }
00401998 lea ecx,[ebp-4]
0040199B call @ILT+5(CTest::~CTest) (0040100a) //<<<<<<<<<<< test生命结束,但其内存数据仍然没变
004019A0 pop edi
004019A1 pop esi
004019A2 pop ebx
004019A3 add esp,44h
004019A6 cmp ebp,esp
004019A8 call __chkesp (00420b10) //<<<<<<<<<<<<<<<函数返回时进行一些处理,0x0012ff1c中的内容变了!所以显示出来的数不再是5
004019AD mov esp,ebp
004019AF pop ebp
004019B0 ret

3. main中的最后一个例子
739: CTest *p2; // <<<<<<<<<<<<p2根本没有初始化,其中的内容为0xcccccccc,当然不能执行
740: p2->Func(); // 函数能够被调用,只是访问m_nTest报访问内存错误.
00401A01 mov ecx,dword ptr [ebp-8]
00401A04 call @ILT+580(CTest::Func) (00401249)

回复
sumash 2004-04-19
你在构造函数和析构函数中加入一段输出测试一下。
#include <iostream>
using namespace std;

class CTest
{
public:
CTest(){ cout << "CTest::Test()\n"; m_nTest = 5;};
~CTest(){ cout << "CTest::~CTest" << endl;}
void Func(void)
{
cout << "Func of class CTest" << endl;
cout << m_nTest << endl;
}
private:
int m_nTest;
};

void Test(void)
{
CTest *p;
{
CTest test;
p = &test;
cout << "Test, before destroy\n";
}
p->Func();
}

void Test2(CTest **p)
{
CTest test;
*p = &test;
cout << "Test2, before destroy\n";
}

void main()
{
cout << "begin Test()\n";
Test();
cout << "end Test()\n";
cout << "begin Test2()\n";
CTest *p;
Test2(&p);
cout << "end Test2()\n";
p->Func();
CTest *p2;
p2->Func();
getchar();
}
我的结果是:
begin Test() //开始Test()
CTest::Test() //构造test
Test, before destroy
CTest::~CTest //在出test作用域就是{}之前test就析构了。。
Func of class CTest
5
end Test()
begin Test2() //
CTest::Test()
Test2, before destroy
CTest::~CTest //在Test2()返回之前test也被析构了
end Test2()
Func of class CTest
-858993460
Func of class CTest
//error here

结论:我觉得Test和Test2是一样子的,不同之处在于一个是返回前调用了func一个是返回后调用的func,变量test被析构只是说他所占用的栈空间被操作系统收回了,能够用在其他需要空间的地方,至于这个地址中的内容则是不可预知的,如果没有被重新分配过,这其内容是不变的,这就是Test可以调用func打印出5的原因,对于Test2,打印的是个未知数,我的解释是函数返回时对他的堆栈进行了操作,修改其中的内容了,所以造成这个结果。。

最后一个问题我也没想明白,望高人指点
回复
caidaol 2004-04-19
1、
CTest *p;
{
CTest test;
p = &test;
}
p->Func(); // 能够正常输出,内存竟然没有释放?
先不说优化的问题,p指向test,test存在于栈里,从test的构造到p->Func(),并没有出栈入栈,仅有两个{},并不影响栈,test虽然过了生存期,但是p还在,程序没有即时清栈,所以test还在,当然能执行。不般不要这样写,最后是new出来,最后delete.
2、
CTest *p2;
p2->Func(); // 函数能够被调用,只是访问m_nTest报访问内存错误.

p2是一个指向CTest的指针,没有初始化,(int)p2是一个随机的。p2->Func()根本就不在。


以上谈了一些个人看法,希望高人予以补充和更正!
回复
RookieStar 2004-04-19
再补充一个,对Test能输出正确的值,估计同cgsw12345(cgsw)说的差不多,一种编译器的优化吧,再听听其他人怎么说。
回复
RookieStar 2004-04-19
下面再看main:

#include "iostream.h"

class CTest
{
public:
CTest(){m_nTest = 5;};
~CTest(){};
void Func(void)
{ cout << "Func of class CTest" << endl;
cout << m_nTest << endl;
}
private:
int m_nTest;
};

void Test(void)
{
CTest *p;
{
CTest test;
p = &test;
}
p->Func(); // 能够正常输出,内存竟然没有释放?
}

void Test2(CTest **p)
{
CTest test;
*p = &test; // 只要存在p=……,就是给p的初始化(p值是个明确的地址),不管这个初始化地址以后是否还存在。不管怎样,只要p没有释放,p的值还是个指针地址。
} //test对象析构释放内存,这时传给p的那个指针虽然指向test所在内存,但这部分内存已经被系统释放回收,所以其内的数据域的数值是不确定的

void main()
{
Test(); //这个不多说了

CTest *p;
Test2(&p); // p不但是个CTest指针,而且所以它空有数据域和各接口,但数据域没有确定的值
p->Func(); // 函数能够正常执行因为p拥有CTest类的所有接口(因为它是CTest类的指针)

CTest *p2; // 你只声明了p2,但没有初始化(p2的值不确定,不同于Test2里面p有确定的值,只是p指向的内容没有确定的值)
p2->Func(); // 函数能够被调用(同样因为它有所有接口),但p2根本连个值也没有,系统为防止意外,所以报错。但你上面一个p的调用是系统无法预见的,这也是为何C++有这样那样的内存泄露问题,所以像这样的使用,我们应该极力避免!!!
}

以上谈了一些个人看法,希望高人予以补充和更正!
回复
RookieStar 2004-04-19
先回答一个:

void Test(void)
{
CTest *p; // p定义在这里,其生存周期从这里起到Test结束
{ // 如果p定义在这里,
CTest test;
p = &test;
} // 就要在该作用域结束时,释放内存
p->Func(); // p的内存没有释放,因为还在作用域内
}// 当这个}结束的时候,p的内存才释放

一个原则:定义的一个对象,向上找离其最近的那个{ ,然后此{ 对应的 }结束后,该对象析构释放内存。
回复
cgsw12345 2004-04-19
to vcchunhong
有分号和没分号是一样,不信你在每个函数的"}"后打上分号,保证不会有错
void Test(void)
{
CTest *p;
{
CTest test;
p = &test;
}
p->Func(); // 能够正常输出,内存竟然没有释放?
}

我觉得是编译器对看到你的{}没有实质的作用,所以你对的代码进行优化
并去掉了{}
最后实质运行的是这样一个函数:
void Test(void)
{
CTest *p;
CTest test;
p = &test;
p->Func(); // 能够正常输出,内存竟然没有释放?
}
所以它能输出正确的结果,个人意见,仅供参考!

回复
vcchunhong 2004-04-19
你着程序有错误
CTest(){m_nTest = 5;};
~CTest(){};
谁告诉你构造函数和析构函数学以后要;的?
把;去掉改成
CTest(){m_nTest = 5;}
~CTest(){}

回复
lemon520 2004-04-19
我对
void Test(void)
{
CTest *p;
{
CTest test;
p = &test;
}
p->Func(); // 能够正常输出,内存竟然没有释放?
}

中p可以正常调用Func()的解释是可能是由于Fucn()的调用在void Test(void)返回之前,因此函数栈并没有释放,虽然test对象确实被析构了,但是它的值因为没有动,所以可以正常输出.

void Test2(CTest **p)
{
CTest test;
*p = &test;
}
在函数返回后函数栈就销毁了。所以之后调用p->Func();会得到一个不确定的值.
我瞎说的!
回复
sharkhuang 2004-04-19
是这样的!非虚礼函数其实就是全局函数.只是名字扩展了.
回复
发动态
发帖子
C++ 语言
创建于2007-09-28

5.9w+

社区成员

C++ 语言相关问题讨论,技术干货分享,前沿动态等
申请成为版主
社区公告
暂无公告