转载请注明出处,版权归作者所有

lyzaily@126.com

yanzhong.lee

作者按:

从这篇文章中,我们主要会认识到一下几点:

一、不类中的特征标相同的同名函数,它们是不同的函数,原因就是类具有“名称空间”的功能;

二、类的对象是不包含类声明中所提到的成员函数所占的内存,对象只包含类声明中非static成员数据,如类声明中有虚函数,则对象还会有个vtbl指针。同一个类的所有对象都是使用同一份成员函数拷贝。

三、VS编译器是如何实现函数的重载的,以及如何使用this指针的。

这是写给C++初学者的文章,高手就不用浪费您宝贵的时间看了。

我们知道在同一个类中函数不能完全相同,当然可以重载了,我所要说的是函数名以及它的特征标完全一样的函数在类中只能有一个,但是在不同的类中可以同时存在函数名以及特征标完全一致的函数,这个是为什么呢?这个就是C++类的功劳,这个有点类似于C++中的“名称空间”功能。

那么这些C++语言特性又是如何得到C++编译支持的呢?

为了研究这个问题,我特地写了一个测试程序如下:

//CTestA类

class CTestA{
public:
 CTestA();
 ~CTestA();
 int func(int param);
 float func(float param);
private:
 int m_value;
 float m_float;
};
int CTestA::func(int param)
{
 m_value += 1;
 return 0;
}
float CTestA::func(float param)
{
 m_float += 1.0;
 return m_float;
}
CTestA::CTestA()
{
 m_value = 0;
 m_float = 0.0;
}
CTestA::~CTestA()
{
}

//CTestB类
class CTestB{
public:
 CTestB();
 ~CTestB();
 int func(int param);
private:
 int m_value;
};

int CTestB::func(int param)
{
 m_value += 1;
 return 0;
}
CTestB::CTestB()
{
 m_value = 0;
}
CTestB::~CTestB()
{
}

//main函数
int _tmain(int argc, _TCHAR* argv[])
{
 float x = 2.0;
 CTestA testA;
 CTestB testB;

testA.func(1);
 testA.func(x);     //OK
 testA.func(2.0f); //Ok
 //testA.func(2.0); //error ambiguous call

testB.func(2);
 return 0;
}

上面是源代码,我们在VS2005中看到的对应的汇编代码是这样的:

--- f:\game\classtest\classtest\classtest.cpp ----------------------------------

int _tmain(int argc, _TCHAR* argv[])
{
00411610  push        ebp  
00411611  mov         ebp,esp 
00411613  push        0FFFFFFFFh 
00411615  push        offset __ehhandler$_wmain (414830h) 
0041161A  mov         eax,dword ptr fs:[00000000h] 
00411620  push        eax  
00411621  sub         esp,0F4h 
00411627  push        ebx  
00411628  push        esi  
00411629  push        edi  
0041162A  lea         edi,[ebp-100h] 
00411630  mov         ecx,3Dh 
00411635  mov         eax,0CCCCCCCCh 
0041163A  rep stos    dword ptr es:[edi] 
0041163C  mov         eax,dword ptr [___security_cookie (418000h)] 
00411641  xor         eax,ebp 
00411643  push        eax  
00411644  lea         eax,[ebp-0Ch] 
00411647  mov         dword ptr fs:[00000000h],eax 
 float x = 2.0;
0041164D  fld         dword ptr [__real@40000000 (416750h)] 
00411653  fstp        dword ptr [ebp-14h] 
 CTestA testA;
00411656  lea         ecx,[ebp-24h] 
00411659  call        CTestA::CTestA (411163h) 
0041165E  mov         dword ptr [ebp-4],0 
 CTestB testB;
00411665  lea         ecx,[ebp-30h] 
00411668  call        CTestB::CTestB (41116Dh) 
0041166D  mov         byte ptr [ebp-4],1

testA.func(1);
00411671  push        1    
00411673  lea         ecx,[ebp-24h] 
00411676  call        CTestA::func (4110F0h) 
 testA.func(x);
0041167B  push        ecx  
0041167C  fld         dword ptr [ebp-14h] 
0041167F  fstp        dword ptr [esp] 
00411682  lea         ecx,[ebp-24h] 
00411685  call        CTestA::func (4110EBh) 
0041168A  fstp        st(0) 
 testA.func(2.0f);
0041168C  push        ecx  
0041168D  fld         dword ptr [__real@40000000 (416750h)] 
00411693  fstp        dword ptr [esp] 
00411696  lea         ecx,[ebp-24h] 
00411699  call        CTestA::func (4110EBh) 
0041169E  fstp        st(0) 
 //testA.func(2.0); //error ambiguous call

testB.func(2);
004116A0  push        2    
004116A2  lea         ecx,[ebp-30h] 
004116A5  call        CTestB::func (4110B9h) 
 return 0;
004116AA  mov         dword ptr [ebp-0FCh],0 
004116B4  mov         byte ptr [ebp-4],0 
004116B8  lea         ecx,[ebp-30h] 
004116BB  call        CTestB::~CTestB (4110FFh) 
004116C0  mov         dword ptr [ebp-4],0FFFFFFFFh 
004116C7  lea         ecx,[ebp-24h] 
004116CA  call        CTestA::~CTestA (411104h) 
004116CF  mov         eax,dword ptr [ebp-0FCh] 
}
004116D5  push        edx  
004116D6  mov         ecx,ebp 
004116D8  push        eax  
004116D9  lea         edx,[ (411708h)] 
004116DF  call        @ILT+135(@_RTC_CheckStackVars@8) (41108Ch) 
004116E4  pop         eax  
004116E5  pop         edx  
004116E6  mov         ecx,dword ptr [ebp-0Ch] 
004116E9  mov         dword ptr fs:[0],ecx 
004116F0  pop         ecx  
004116F1  pop         edi  
004116F2  pop         esi  
004116F3  pop         ebx  
004116F4  add         esp,100h 
004116FA  cmp         ebp,esp 
004116FC  call        @ILT+345(__RTC_CheckEsp) (41115Eh) 
00411701  mov         esp,ebp 
00411703  pop         ebp  
00411704  ret              
00411705  lea         ecx,[ecx] 
00411708  db          02h  
00411709  db          00h  
0041170A  db          00h  
0041170B  db          00h  
0041170C  db          10h  
0041170D  db          17h  
0041170E  db          41h  
0041170F  db          00h  
00411710  db          dch  
00411711  db          ffh  
00411712  db          ffh  
00411713  db          ffh  
00411714  db          08h  
00411715  db          00h  
00411716  db          00h  
00411717  db          00h  
00411718  db          2eh  
00411719  db          17h  
0041171A  db          41h  
0041171B  db          00h  
0041171C  db          d0h  
0041171D  db          ffh  
0041171E  db          ffh  
0041171F  db          ffh  
00411720  db          04h  
00411721  db          00h  
00411722  db          00h  
00411723  db          00h  
00411724  db          28h  
00411725  db          17h  
00411726  db          41h  
00411727  db          00h  
00411728  db          74h  
00411729  db          65h  
0041172A  db          73h  
0041172B  db          74h  
0041172C  db          42h  
0041172D  db          00h  
0041172E  db          74h  
0041172F  db          65h  
00411730  db          73h  
00411731  db          74h  
00411732  db          41h  
00411733  db          00h  
--- No source file -------------------------------------------------------------

我们看到汇编代码中用蓝色标出的两条汇编语句,就是两条调用两个类中func函数的跳转语句。

00411676  call        CTestA::func (4110F0h) 
004116A5  call        CTestB::func (4110B9h) 
其中,00411676和004116A5是这个两个语句的本身地址,而在每条语句的最后面用括号括起来的,如 (4110F0h) 
和(4110B9h) 都是call指令要去的目的地址,这还不是func函数所在内存的地址,只是一个跳转指令的地址。我们可以看出这个两个函数的地址是不一样的,这个就说明两个类中具有相同特征标的同名方法,在内存中有不占据不同的内存区域。那么编译器是如何区别这样的函数呢,我们从上面蓝色标志的两句汇编语句就可以知道,是使用了类的作用域来表示,如CTestA::和CTestB::,这类似于“名称空间”功能。

下面的代码片段,就是在语句“00411676  call        CTestA::func (4110F0h) ”处按F11跳转到的目的地。如下面红色标志的一句。

004110F0  jmp         CTestA::func (4113F0h)

这红色标志的语句jmp CTestA::func(4113F0h)才真正的跳到类CTestA中的func内存处,而该函数的首地址就是4113F0h,如下代码段(红色标志的一句):

int CTestA::func(int param)
{
004113F0  push        ebp  
004113F1  mov         ebp,esp 
004113F3  sub         esp,0CCh 
004113F9  push        ebx  
004113FA  push        esi  
004113FB  push        edi  
004113FC  push        ecx  
004113FD  lea         edi,[ebp-0CCh] 
00411403  mov         ecx,33h 
00411408  mov         eax,0CCCCCCCCh 
0041140D  rep stos    dword ptr es:[edi] 
0041140F  pop         ecx  
00411410  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411413  mov         eax,dword ptr [this] 
00411416  mov         ecx,dword ptr [eax] 
00411418  add         ecx,1 
0041141B  mov         edx,dword ptr [this] 
0041141E  mov         dword ptr [edx],ecx 
 return 0;
00411420  xor         eax,eax 
}
00411422  pop         edi  
00411423  pop         esi  
00411424  pop         ebx  
00411425  mov         esp,ebp 
00411427  pop         ebp  
00411428  ret         4    
--- No source file -------------------------------------------------------------

同理我们看看第二条语句“call        CTestB::func (4110B9h”该语句是跳到地址4110B9h处,如下面的代码:

004110B9  jmp         CTestB::func (411530h)

该语句是一条跳转指令,同上,该指令会跳到正真的成员函数所在的内存地址处(00411530 ),代码如下所示:

int CTestB::func(int param)
{
00411530  push        ebp  
00411531  mov         ebp,esp 
00411533  sub         esp,0CCh 
00411539  push        ebx  
0041153A  push        esi  
0041153B  push        edi  
0041153C  push        ecx  
0041153D  lea         edi,[ebp-0CCh] 
00411543  mov         ecx,33h 
00411548  mov         eax,0CCCCCCCCh 
0041154D  rep stos    dword ptr es:[edi] 
0041154F  pop         ecx  
00411550  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411553  mov         eax,dword ptr [this] 
00411556  mov         ecx,dword ptr [eax] 
00411558  add         ecx,1 
0041155B  mov         edx,dword ptr [this] 
0041155E  mov         dword ptr [edx],ecx 
 return 0;
00411560  xor         eax,eax 
}
00411562  pop         edi  
00411563  pop         esi  
00411564  pop         ebx  
00411565  mov         esp,ebp 
00411567  pop         ebp  
00411568  ret         4

小结一下:

不同类中具有相同特征标的同名方法,编译器是通过类的作用域操作符(::)来区分的,这样的函数分别有自己的内存空间,这点可以从上面它们具有不同的内存地址可以得到证实。

同一个类的所有实例对象都是使用内存中的同一组成员方法拷贝,所以类的成员方法不属于任何一个对象,所有的对象共享内存中的同一份成员函数拷贝;因此一个类的对象只包含类声明中的数据部分,包括一个VTBL指针数据(如果该类中有virtual成员函数的话),那么如果某个对象要使用类声明中的某个成员函数怎么办呢?这个就涉及到C++编译中引入的this指针了,这this指针就是指向调用成员函数的对象内存的地址;我们以程序为证:

我修改过了上面的main函数,如下:

主要看下面用红色标志的地方,我用同一类声明了两个对象testA和testA2,然后我们再看看VS2005编译是如何处理对象调用函数的,还是看VS给出的汇编代码吧。

int _tmain(int argc, _TCHAR* argv[])
{
 float x = 2.0;
 CTestA testA;
 CTestA testA2;
 CTestB testB;

 testA.func(1);
 testA2.func(4);
 testA.func(x);
 testA.func(2.0f);
 //testA.func(2.0); //error ambiguous call

testB.func(2);
 return 0;
}

汇编代码如如下:

这次我只给出有关这两个对象的汇编代码片段:

testA.func(1);
0041167D  push        1    
0041167F  lea         ecx,[ebp-24h] 
00411682  call        CTestA::func (4110F0h) 
 testA2.func(4);
00411687  push        4    
00411689  lea         ecx,[ebp-34h] 
0041168C  call        CTestA::func (4110F0h)

可以看出一个类的不同对象,调用的成员函数func地址是一样的,这就证明了我们上面的结论 ——“一个类的不同对象是共享同内存处的同一组函数拷贝”。

那么我们接下来就是要证明的就是对象只包含数据不包含函数而且成员函数是通过this指针来区别同一类的不同对象的????

我们还是看汇编代码吧,我们在上面的两条红色标志的代码处分别按下F11两次,第一次按F11是跳到如下语句

004110F0  jmp         CTestA::func (4113F0h)

第二次按F11就会到达成员函数func所在的内存处,汇编代码如下:

int CTestA::func(int param)
{
004113F0  push        ebp  
004113F1  mov         ebp,esp 
004113F3  sub         esp,0CCh 
004113F9  push        ebx  
004113FA  push        esi  
004113FB  push        edi  
004113FC  push        ecx  
004113FD  lea         edi,[ebp-0CCh] 
00411403  mov         ecx,33h 
00411408  mov         eax,0CCCCCCCCh 
0041140D  rep stos    dword ptr es:[edi] 
0041140F  pop         ecx  
00411410  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411413  mov         eax,dword ptr [this] 
00411416  mov         ecx,dword ptr [eax] 
00411418  add         ecx,1 
0041141B  mov         edx,dword ptr [this] 
0041141E  mov         dword ptr [edx],ecx 
 return 0;
00411420  xor         eax,eax 
}

看到没有在操作具体数据时用到了this指针,我们用VS提供的QuichWatch功能,就看到this的地址,以及它所指向的对象的值:

this值为0x0012ff44

this所指向的对象值为{m_value=0 m_float=0.00000000}

我们还没有证明我们刚才所说的结论,还必须要看一下this所指向的下一个对象的地址,也就说在用下一个对象(testA2)调用成员函数时的this指针的值。

还是看汇编代码比较有说服力:

int CTestA::func(int param)
{
004113F0  push        ebp  
004113F1  mov         ebp,esp 
004113F3  sub         esp,0CCh 
004113F9  push        ebx  
004113FA  push        esi  
004113FB  push        edi  
004113FC  push        ecx  
004113FD  lea         edi,[ebp-0CCh] 
00411403  mov         ecx,33h 
00411408  mov         eax,0CCCCCCCCh 
0041140D  rep stos    dword ptr es:[edi] 
0041140F  pop         ecx  
00411410  mov         dword ptr [ebp-8],ecx 
 m_value += 1;
00411413  mov         eax,dword ptr [this] 
00411416  mov         ecx,dword ptr [eax] 
00411418  add         ecx,1 
0041141B  mov         edx,dword ptr [this] 
0041141E  mov         dword ptr [edx],ecx 
 return 0;
00411420  xor         eax,eax 
}
代码和上面是一摸一样的,不同的就是this值,使用同样的方法查看this值:

this值为0x0012ff34

可以明显的看出此处的this值不等于上面的this值(0x0012ff34 != 0x0012ff44),这就证明了我们的编译器使用this指针来区别同一类的不同对象,已经对象只包含类声明时的数据(注意如果是static数据成员 就不会包含在对象中;只有类声明中包含virtual成员函数是对象中才有vtbl指针,这些留给读者可以自己去验证!)

转载于:https://my.oschina.net/N3verL4nd/blog/866930

编译器如何处理C++不同类中同名函数(参数类型个数都相同)的更多相关文章

  1. python函数参数类型及其顺序

    根据inspect模块官文文档中关于函数参数类型的相关说明,python函数参数共有五种类型,按顺序分别为:POSITIONAL_ONLY.POSITIONAL_OR_KEYWORD.VAR_POSI ...

  2. 【java&c++】父子类中同名函数的覆盖问题

    java和c++两门语言对于父子类中同名函数具有不同的处理方式. 先上两段代码: C++: class Basic { public: void test(string i){ cout <&l ...

  3. Excel中COUNTIFS函数统计词频个数出现次数

    Excel中COUNTIFS函数统计词频个数出现次数   在Excel中经常需要实现如下需求:在某一列单元格中有不同的词语,有些词语相同,有的不同(如图1所示).需要统计Excel表格中每个词语出现的 ...

  4. javascript中所有函数参数都是按值传递

    在看<JavaScript高级程序设计>(第三版)的时候,传递参数这一节,里面提到 ECMAScript中所有函数的参数都是按值传递的 它自己的解释是, 把函数外部的值复制给函数内部的参数 ...

  5. 关于C/C++中main函数参数的学习

    因为面对对象作业(2018.5.21)的要求,去学习了C/C++中main函数参数的意义,以及一些简单的使用(从命令行指令的接受),不给予赘述.(仅为个人拙见,还望看官指正) 首先,带有参数的main ...

  6. JS中给函数参数添加默认值(多看课程)

    JS中给函数参数添加默认值(多看课程) 一.总结 一句话总结:咋函数里面是可以很方便的获取调用函数的参数的,做个判断就好,应该有简便方法,看课程. 二.JS中给函数参数添加默认值 最近在Codewar ...

  7. Python 带参数的装饰器 [2] 函数参数类型检查

    在Python中,不知道函数参数类型是一个很正常的事情,特别是在一个大项目里.我见过有些项目里,每一个函数体的前十几行都在检查参数类型,这实在是太麻烦了.而且一旦参数有改动,这部分也需要改动.下面我们 ...

  8. Python 函数参数类型大全(非常全!!!)

    Python 函数参数类型大全(非常全!!!) 1.在python编写程序里面具有函数文档,它的主要作用是为了让别人可以更好的理解你的函数,所以这是一个好习惯,访问函数文档的方式是: MyFuncti ...

  9. 【Unity|C#】基础篇(4)——函数参数类型(值参/ref/out/params)

    [学习资料] <C#图解教程>(第5章):https://www.cnblogs.com/moonache/p/7687551.html 电子书下载:https://pan.baidu.c ...

随机推荐

  1. Java课程设计---删除学生

    1.界面已经在上次修改操作的过程添加完成 2.在StudentDao中添加删除方法 public boolean delete(int id) throws SQLException { DbUtil ...

  2. WPS:添加公式后,行间距变宽的解决方法

    找到公式所属段落的样式,右键修改样式 左下角'格式'中选择'段落' 段落间距设置为0,不要勾选与文档网格对齐

  3. 进程&线程(二):Thread相关方法与属性

    学习自:python进程.线程.协程 - 张岩林 - 博客园 1.threading.Thread Thread方法 方法(使用方法为Thread.xxx) 说明 start() 激活线程 getNa ...

  4. 微信小程序商品发布

    <!--pages/good/good.wxml--> <!--商品发布--> <form bindsubmit="formSubmit"> & ...

  5. 浏览器中 Http缓存

    分类: web缓存主要有:数据库缓存.服务器缓存(代理服务器缓存.CDN缓存),浏览器缓存. 数据库缓存 当web应用的关系比较复杂,数据库中的表很多的时候,如果频繁进行数据库查询,很容易导致数据库不 ...

  6. 当.Net撞上BI可视化,这3种“套路”你必须知道

    最近葡萄在做技术支持,又遇到了客户给我们出的新问题. 事情是这样的. 这次客户使用的是.Net项目,直接做BI大屏过于复杂,所以想直接集成使用BI数据可视化分析大屏. 所以,这次我们就从--Wyn出发 ...

  7. C++移动语义 详细讲解【Cherno C++教程】

    移动语义 本文是对<最好的C++教程>的整理,主要是移动语义部分,包含视频85p左值和右值.89p移动语义与90p stdmove和移动赋值操作符. 移动语义是C++11的新feature ...

  8. vue3-关于$props,$parents等引用元素和组件的注意事项

    同一个组件内可以使用,但是在不同的组件内,不要用$parents或$refs来访问另一个组件内的数据, 这会使代码的耦合性变高,同时也会让代码的可读性变差, 在不同组件访问数据时,使用props等来传 ...

  9. 用 EXISTS 或 NOT EXISTS 用法

    项目中遇到这么个情况: t1表 和 t2表  都是150w条数据,600M的样子,都不算大. 但是这样一句查询 ↓ select * from t1 where phone not in (selec ...

  10. Java数组经典例题

    数组中元素的求和 public class T02 { public static void main(String[] args) { int[][]arr=new int[][]{{1,2,3,4 ...