空指標的成員函式呼叫
我一直認為技術是沒有止境的,不管你怎麼去學,總有你沒有掌握的地方。但是,人,是不能停下腳步的。
今天在檢查一個MFC程式,看到GetSafeHwnd函式,於是讓我想明白到底它比m_hWnd成員變數safe在哪裡?到網上查了一下資料,發現了GetSafeHwnd的實現:
_AFXWIN_INLINE HWND CWnd::GetSafeHwnd() const {
return this == NULL?NULL:m_hWnd;
}
開始一看感覺不會吧?這有意義麼?this為NULL了,函式還能呼叫嗎?於是寫了一個簡單的程式來做測試,程式碼如下:
class A {
public:
int i;
void f() {
cout<<"Hello"<<endl;
}
};
void main() {
A * p= NULL;
p->f();
}
測試發現,程式能夠正常執行。把p賦一個非空值如p=(A*)123; 同樣如此。於是想搞明白這到底是怎麼一回事。雖然以前明白類成員函式中其實是隱藏了一個this指標,但不同的例項在呼叫函式時是如何工作的,還不是很清楚。通過這個測試,才能我徹底明白。
對於一個類,在編譯好的彙編程式碼中,只有一份程式碼拷貝,但是有不同的例項空間。比如我們定義A a,b;我們呼叫a.f()和b.f()時,會跳轉到類程式碼中f()函式的實現部分。
但如果你要引用成員變數,比如a.i,那麼程式需要找到a在記憶體中的地址,然後通過便宜量找到成員i的地址。如果f()中加上一句cout<<i<<endl的話,上面的程式碼很顯然會崩潰,為什麼呢?
在函式的彙編程式碼中,有一個變數this,你在做函式類的函式呼叫的時候,類似與函式引數一樣壓到棧上去,雖然不同例項執行了同一段程式碼,因為this不同,得到的結果當然不同。如果你要存取成員變數,而this是一個非法地址,當然會引起崩潰。
綜上可知,即時一個函式呼叫正常,還不能確定指標非空,很可能是因為成員函式中沒有使用成員變數的緣故。看來,這還真是一個搞笑的bug。
這是我自己的一段程式碼:
int _tmain(int argc, _TCHAR* argv[])
{
string leftString("test ");
string rightString(
dstring *praxis =new dstring();
praxis->praxis1(leftString, rightString);
delete praxis;
praxis = NULL;
cout << praxis->praxis2() <<endl;
system("pause");
return0;
}
delete釋放了praxis所指向的堆地址中的資料,並且指標被賦為空了, 當呼叫praxis->praxis2()時,雖然這個時候praxis指標已經空了,但由於Praxis2方法中沒有使用成員變數,所以,程式並沒有試圖去訪問堆,也就不會引起error或者將系統搞崩潰。
下面是全部的程式碼:
// dstring.h
//
#ifndef DSTRING_H
#define DSTRING_H#pragma once
#include <string>usingnamespace std;
class dstring
{
public:
dstring(void);
void praxis1(const std::string&leftStr, const std::string&rightStr);
string praxis2(void);
string praxis3(void);
public:
~dstring(void);
};
#endif// dstring.cpp
//
#include "StdAfx.h"
#include "dstring.h"
#include <string>
#include <stdio.h>
#include <iostream>
#include <cctype>
dstring::dstring(void)
{
}
dstring::~dstring(void)
{
}
void dstring::praxis1(conststring&leftStr, conststring&rightStr)
{
if (leftStr == rightStr)
{
cout<<leftStr<<" and "<<rightStr<<"is equal"<<endl;
}
else
{
if (leftStr > rightStr)
{
cout<<leftStr<<" is bigger than "<<rightStr<<endl;
}
else
{
cout<<leftStr<<" is smaller than "<<rightStr<<endl;
}
}
}
string dstring::praxis2(void)
{
string sentence, article;
bool firstEnter =true;
cout <<" Please enter sentences and end with Ctrl + Z."<< endl;
while (cin >> sentence)
{
if (!firstEnter)
{
sentence =""+ sentence;
}
article += sentence;
firstEnter =false;
}
return article;
}
string dstring::praxis3(void)
{
string sentence, article;
cout <<"Please enter a sentence with punctuatio."<<endl;
//cin >> sentence;while (cin >> sentence)
{
article += sentence;
}
for (string::size_type index =0; index != article.size(); index ++)
{
if (ispunct(article[index]))
{
article[index] ='';
}
}
return article;
}