万星星@豌豆荚 欢迎加入我们
一个吃软饭的男人!!!!!我只想写程序####
微博:http://weibo.com/wanlianwen
posts - 172,  comments - 1253,  trackbacks - 0

经常存在这样一种情况,写代码的时候按照一种固有思维去写,可能时间久了都没有考虑过为什么这么写,不这么写会引起什么错误。我时常有这样的困惑,了解太少,有时候遇到蹊跷的事情也没时间探究。
今天在跟踪mfc库的时候,遇到一件事情使我非常惊讶,彻底打破了我对空对象指针的看法。在我的概念里面,空的对象指针是不能使用的,不能调用方法。不知道大家有没有这样的想法,事实证明是不对的。下面看看我跟踪的代码:

CWnd *  PASCAL CWnd::FromHandle(HWND hWnd)
{
    CHandleMap
*  pMap  =  afxMapHWND(TRUE);  // create map if not exist
    ASSERT(pMap  !=  NULL);
    CWnd
*  pWnd  =  (CWnd * )pMap -> FromHandle(hWnd);

#ifndef _AFX_NO_OCC_SUPPORT
    pWnd
-> AttachControlSite(pMap);
#endif

    ASSERT(pWnd 
==  NULL  ||  pWnd -> m_hWnd  ==  hWnd);
    
return  pWnd;
}
上面代码中,pWnd指针是空的,然而可以调用方法。我百思不得其解,无奈只有写一个例子看看。
#include <iostream>
using namespace std;

class CObj
{
public:
    
void Test();
public:
    
static int m_iInt;
}
;

int CObj::m_iInt = 100;

void CObj::Test()
{
    cout 
<< m_iInt << endl;
}


int main(int argc, char* argv[])
{
    CObj
*        pObj    = (CObj*)1;
    pObj
->Test();
    ((CObj
*)0)->Test();
    ((CObj
*)1)->Test();
    CObj        obj;
    obj.Test();
    
return 0;
}
一个类,包含一个静态变量和一个实例方法。可以通过空指针,非法指针访问静态变量和实例方法,前提是方法里面不可以访问成员变量,否则在运行期出现非法访问错误,因为对象在内存中不存在,而方法是存在的。我开始怀疑静态方法的威力了,它只不过提供了编译期成员的安全访问罢了。

下面看看汇编,因为只有通过汇编才能了解更深的问题:
25:       CObj*       pObj    = (CObj*)1;
00401848   mov         dword ptr [ebp-4],1
26:       pObj->Test();
0040184F   mov         ecx,dword ptr [ebp
-4]
00401852   call        @ILT+565(CObj::Test) (0040123a)
27:       ((CObj*)0)->Test();
00401857   xor         ecx,ecx
00401859   call        @ILT+565(CObj::Test) (0040123a)
28:       ((CObj*)1)->Test();
0040185E   mov         ecx,
1
00401863   call        @ILT+565(CObj::Test) (0040123a)
29:       CObj        obj;
30:       obj.Test();
00401868   lea         ecx,[ebp-8]
0040186B   call        @ILT
+565(CObj::Test) (0040123a)
俺不懂汇编,临时查了一下,献丑了。
ecx里面存放的是对象的this指针,@ILT是函数的入口点,调用时将ecx传递到函数内部,其实对于编译器函数是全局的,函数调用的时候都需要传递this指针作为第一个参数传递过去。所以可以上面的代码在内存访问方面都是合法的,一旦访问this的非静态成员变量,势必导致非法内存访问。

顺便侃侃编译器优化,我不懂到底那个操作效率高,只谈谈区别。
((CObj*)0)->Test();((CObj*)1)->Test();对应的汇编:
00401857   xor         ecx,ecx
00401859   call        @ILT+565(CObj::Test) (0040123a)

0040185E   mov         ecx,
1
00401863   call        @ILT+565(CObj::Test) (0040123a)
靠,这都有区别,0指针的时候异或一下刚好是0。无奈啊,编译器确实好智能。
posted on 2006-09-20 11:49 万连文 阅读(1271) 评论(3)  编辑 收藏 引用 所属分类: 乱七八糟

FeedBack:
# re: 由一个空对象指针引起的思考
2006-09-20 12:23 | ^_^
静态方法当然可以调用啦……
汇编生成0在我的记忆中编译器一直是这样做的。  回复  更多评论
  
# re: 由一个空对象指针引起的思考
2006-09-20 12:45 | LOGOS

呵呵。如果从汇编,甚至机器码的角度来看待程序,那么,很多语言特性将不复存在。因为这些特性都是在编译期得到保证的,比如:public, const等。
这也就是,很多时候凭着一个指针就可以在C/C++中走天下了。
不过对于接口类(全是纯虚函数),透过指针的操作要三思而行。  回复  更多评论
  
# re: 由一个空对象指针引起的思考
2006-09-20 14:00 | eXile
空对象指针主要用在一些 hacker 方面,比如取得类成员的偏移量:
#define offsetof(ClassType, Member) (size_t) &(((ClassType*)0)->Member)

不过我觉得还是少用为妙
  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


简历下载
联系我

<2006年9月>
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567

常用链接

留言簿(66)

随笔分类

随笔档案

相册

搜索

  •  

最新评论

阅读排行榜

评论排行榜