C++類的空指針調(diào)用成員函數(shù)的代碼
類的實(shí)例調(diào)用成員函數(shù)的原理
其實(shí)不管是通過對象實(shí)例或指針實(shí)例調(diào)用,其實(shí)底層調(diào)用的過程都是一樣的,都是把當(dāng)前對象的指針作為一個(gè)參數(shù)傳遞給被調(diào)用的成員函數(shù)。通過下面的相關(guān)實(shí)例代碼進(jìn)行檢驗(yàn):
實(shí)驗(yàn)的C++代碼
class Student
{
private:
int age;
public:
Student() {}
Student(int age) : age(age) {}
int getAge() { return this->age; }
};
int main(int argc, char const *argv[])
{
Student s(10);
int age = s.getAge();
Student* ps = new Student(10);
age = ps->getAge();
return 0;
}
基于VS2015調(diào)試功能的反匯編代碼
int main(int argc, char const *argv[])
{
00A41860 push ebp
00A41861 mov ebp,esp
00A41863 push 0FFFFFFFFh
00A41865 push 0A461D2h
00A4186A mov eax,dword ptr fs:[00000000h]
00A41870 push eax
00A41871 sub esp,104h
00A41877 push ebx
00A41878 push esi
00A41879 push edi
00A4187A lea edi,[ebp-110h]
00A41880 mov ecx,41h
00A41885 mov eax,0CCCCCCCCh
00A4188A rep stos dword ptr es:[edi]
00A4188C mov eax,dword ptr [__security_cookie (0A4B004h)]
00A41891 xor eax,ebp
00A41893 mov dword ptr [ebp-10h],eax
00A41896 push eax
00A41897 lea eax,[ebp-0Ch]
00A4189A mov dword ptr fs:[00000000h],eax
Student s(10);
00A418A0 push 0Ah /*構(gòu)造函數(shù)中的實(shí)參值: 10 */
00A418A2 lea ecx,[s] /*取實(shí)例s的內(nèi)存地址,并賦值給寄存器ECX*/
00A418A5 call Student::Student (0A4103Ch) /*調(diào)用位于內(nèi)存地址0A4103Ch的構(gòu)造函數(shù)*/
int age = s.getAge();
00A418AA lea ecx,[s] /*取實(shí)例s的內(nèi)存地址,并賦值給寄存器ECX*/
00A418AD call Student::getAge (0A412D5h) /*調(diào)用位于內(nèi)存地址0A412D5h的成員函數(shù)*/
00A418B2 mov dword ptr [age],eax /*成員函數(shù)的返回值賦給變量age*/
Student* ps = new Student(10);
00A418B5 push 4 /*類實(shí)例所需的內(nèi)存大小,有一個(gè)int類型成員,故為4字節(jié)(32位編譯)*/
00A418B7 call operator new (0A412A3h) /*調(diào)用全局的內(nèi)存分配函數(shù),類似C的malloc函數(shù)*/
00A418BC add esp,4 /*棧頂加4個(gè)字節(jié),用于保存operator new內(nèi)存分配返回的內(nèi)存地址值*/
00A418BF mov dword ptr [ebp-108h],eax /*eax存的就是返回的內(nèi)存地址,賦值到ptr [ebp-108h]這是處于棧中的內(nèi)存*/
00A418C5 mov dword ptr [ebp-4],0
00A418CC cmp dword ptr [ebp-108h],0
00A418D3 je main+8Ah (0A418EAh) /*與上一句聯(lián)動,如果分配的地址為0,即失敗,跳轉(zhuǎn)到0A418EAh*/
00A418D5 push 0Ah /*調(diào)用構(gòu)造函數(shù)的實(shí)參值: 10*/
00A418D7 mov ecx,dword ptr [ebp-108h] /*取指針ps指向的實(shí)例的內(nèi)存地址值并賦值給寄存器ECX*/
00A418DD call Student::Student (0A4103Ch) /*調(diào)用位于內(nèi)存地址 0A4103Ch 的構(gòu)造函數(shù)*/
00A418E2 mov dword ptr [ebp-110h],eax
00A418E8 jmp main+94h (0A418F4h)
00A418EA mov dword ptr [ebp-110h],0
00A418F4 mov eax,dword ptr [ebp-110h]
00A418FA mov dword ptr [ebp-0FCh],eax
00A41900 mov dword ptr [ebp-4],0FFFFFFFFh
00A41907 mov ecx,dword ptr [ebp-0FCh]
00A4190D mov dword ptr [ps],ecx
age = ps->getAge();
00A41910 mov ecx,dword ptr [ps] /*取實(shí)例s的內(nèi)存地址值并賦值給寄存器ECX*/
00A41913 call Student::getAge (0A412D5h) /*調(diào)用位于內(nèi)存地址 0A412D5h 的成員函數(shù)*/
age = ps->getAge();
00A41918 mov dword ptr [age],eax
return 0;
00A4191B xor eax,eax
}
分析
通過源碼與匯編代碼的對比,就可以知道,其實(shí)成員函數(shù)與類的實(shí)例是沒有綁定關(guān)系,成員函數(shù)是屬于類的,在內(nèi)存中僅僅只是一個(gè)有效的內(nèi)存地址。對于成員函數(shù)中需要用到實(shí)例,就通過寄存器ECX傳遞過來。
這里引起一下思考,為什么不通過棧傳遞呢?
首先要明白,棧是位于內(nèi)存的,而寄存器是位于CPU的,這二者的讀寫速率就天差地別了, 這是效率的優(yōu)化;而且只要約定處于當(dāng)前成員函數(shù)中時(shí),不對ECX寄存器進(jìn)行修改就好。
但如果使用棧進(jìn)行傳遞,因?yàn)闂J且粋€(gè)動態(tài)的內(nèi)存空間,這就不便于跟蹤與維護(hù)當(dāng)前實(shí)例的地址,就算是使用EBP和ESP去維護(hù),就需要加上一個(gè)偏移量,這對讀寫效率添加了負(fù)擔(dān)。如果維護(hù)不好,還會造成數(shù)據(jù)錯(cuò)亂。(這里都是個(gè)人分析, 有錯(cuò)可以指正)
回歸標(biāo)題
所以回歸到標(biāo)題,如果一個(gè)類的空指針調(diào)用了成員函數(shù)后,編譯是通過的。接著在運(yùn)行過程中,分兩種情況:
一、當(dāng)被調(diào)用的成員函數(shù)中,未使用this指針去調(diào)用當(dāng)前實(shí)例的成員變量,程序正常運(yùn)行不報(bào)錯(cuò);
class Student
{
private:
int age;
public:
Student() {}
Student(int age) : age(age) {}
void eat() {}
};
int main(int argc, char const *argv[])
{
Student* ps = nullptr;
ps->eat();
return 0;
}
二、當(dāng)被調(diào)用的成員函數(shù)中,使用了this指針去讀寫當(dāng)前實(shí)例的成員變量時(shí),首先調(diào)用是成員函數(shù)是被成功調(diào)用的,代碼的執(zhí)行已經(jīng)進(jìn)入了成員函數(shù)的領(lǐng)空,但當(dāng)代碼執(zhí)行到讀寫當(dāng)前實(shí)例的成員變量里,就會報(bào)內(nèi)存訪問異常了。
最后發(fā)散思考
回想其中一句話:成員函數(shù)是屬于類的,而不屬于實(shí)例的,會不會引發(fā)你的思考,類的靜態(tài)函數(shù),也是屬于類的, 那如果用類的空指針調(diào)用靜態(tài)函數(shù),又會發(fā)生什么事呢?上代碼:
#include <iostream>
using namespace std;
class Student
{
private:
int age;
public:
Student() {}
Student(int age) : age(age) {}
static int classtime() {
return 9;
}
void eat() {}
};
int main(int argc, char const *argv[])
{
Student* ps = nullptr;
int time = ps->classtime();
cout << time << endl;
return 0;
}
經(jīng)過實(shí)驗(yàn)就會發(fā)現(xiàn)這代碼是可以成功運(yùn)行的。其實(shí)原理上面都說了, 這里就不贅述了。主要的一點(diǎn)是類的靜態(tài)函數(shù)絕對不會操作到某一實(shí)例的成員數(shù)據(jù),所以這種調(diào)用是安全的。但在實(shí)際編程中, 這個(gè)方法應(yīng)該不會被使用到吧(個(gè)人觀點(diǎn),用類名調(diào)用它不香嗎,還有去寫一個(gè)類指針再調(diào)用)
總結(jié)
面試的時(shí)候被問到了, 自己會, 但不能回答清晰也是罪,所以在這里總結(jié)一下??!
到此這篇關(guān)于C++類的空指針調(diào)用成員函數(shù)的文章就介紹到這了,更多相關(guān)C++類空指針調(diào)用成員函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++函數(shù)指針和回調(diào)函數(shù)使用解析
這篇文章主要為大家詳細(xì)介紹了C++函數(shù)指針和回調(diào)函數(shù)的使用,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10
C++多重繼承引發(fā)的重復(fù)調(diào)用問題與解決方法
這篇文章主要介紹了C++多重繼承引發(fā)的重復(fù)調(diào)用問題與解決方法,結(jié)合具體實(shí)例形式分析了C++多重調(diào)用中的重復(fù)調(diào)用問題及相應(yīng)的解決方法,需要的朋友可以參考下2018-05-05
C語言數(shù)據(jù)結(jié)構(gòu)之鏈隊(duì)列的基本操作
這篇文章主要為大家介紹了C語言之鏈隊(duì)列的基本操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2021-12-12
用代碼和UML圖化解設(shè)計(jì)模式之橋接模式的深入分析
本篇文章是對橋接模式進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05

