C++不同类中的特征标相同的同名函数
转载请注明出处,版权归作者所有
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指针,这些留给读者可以自己去验证!)
C++不同类中的特征标相同的同名函数的更多相关文章
- 编译器如何处理C++不同类中同名函数(参数类型个数都相同)
转载请注明出处,版权归作者所有 lyzaily@126.com yanzhong.lee 作者按: 从这篇文章中,我们主要会认识到一下几点: 一.不类中的特征标相同的同名函数,它们是不同的函数,原因就 ...
- SLAM算法中提取特征总结
我们要知道三维空间中的点在图像中的位置,就需要提取特征与特征匹配了. 1.检测特征点 2.计算描述子 3.特征匹配 1.检测特征点 我们用到的检测特征点的方法是FAST算法,最大的特点就是快! 算法原 ...
- Android studio新建文件出现setContentView(R.layout.activity_main);中的R标红错误解决方法
今天打开Android studio突然出现了setContentView(R.layout.activity_main);中的R标红错误,这已经不是第一次出现这个错误了,真心的觉得Android s ...
- 机器学习中的特征缩放(feature scaling)
参考:https://blog.csdn.net/iterate7/article/details/78881562 在运用一些机器学习算法的时候不可避免地要对数据进行特征缩放(feature sca ...
- 在 CSS 中使用特征查询
原文地址:Using Feature Queries in CSS 原文作者:Jen Simmons 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:Che ...
- 深度CTR预估模型中的特征自动组合机制演化简史 zz
众所周知,深度学习在计算机视觉.语音识别.自然语言处理等领域最先取得突破并成为主流方法.但是,深度学习为什么是在这些领域而不是其他领域最先成功呢?我想一个原因就是图像.语音.文本数据在空间和时间上具有 ...
- VLFeat中SIFT特征点检测
本代码使用VLFeat库中的函数对一幅图像进行了SIFT检测 需要事先配置好VLFeat和OpenCV,VLFeat的配置参考前一篇博文,OpenCV的配置网上一大堆,自己去百度 #include & ...
- 相同类中方法间调用时日志Aop失效处理
本篇分享的内容是在相同类中方法间调用时Aop失效处理方案,该问题我看有很多文章描述了,不过大多是从事务角度分享的,本篇打算从日志aop方面分享(当然都是aop,失效和处理方案都是一样),以下都是基于s ...
- c#关于数据和方法在不同类中的引用-xdd
关于数据和方法在不同类中的引用 using System; using System.Collections.Generic; using System.Linq; using System.Text ...
随机推荐
- 使用ASP.NET Core 3.x 构建 RESTful API - 4.2 过滤和搜索
向Web API传递参数 数据可以通过多种方式来传给API. Binding Source Attributes 会告诉 Model 的绑定引擎从哪里找到绑定源. 共有以下六种 Binding Sou ...
- 1087 有多少不同的值 (20 分)C语言
当自然数 n 依次取 1.2.3.--.N 时,算式 ⌊n/2⌋+⌊n/3⌋+⌊n/5⌋ 有多少个不同的值?(注:⌊x⌋ 为取整函数,表示不超过 x 的最大自然数,即 x 的整数部分.) 输入格式: ...
- 小小知识点(十七)——对数形式功率(dBm)与非对数形式功率(w)之间的换算关系
摘自https://blog.csdn.net/shij19/article/details/52946454 dBm 物理含义是:一个表示功率绝对值的值(也可以认为是以1mW功率为基准的一个比值) ...
- NSOperationQueue队列依赖相关思考
添加依赖后,队列中网络请求任务有依赖关系时,任务结束判定以数据返回为准还是以发起请求为准? waitUntilFinished方法容易误解. 依赖关系 // // ViewController.m / ...
- Spring MVC系列之模型绑定(SpringBoot)(七)
前言 上一节我们在SpringBoot中启用了Spring MVC最终输出了HelloWorld,本节我们来讲讲Spring MVC中的模型绑定,这个名称来源于.NET或.NET Core,不知是否恰 ...
- Ubuntu生成应用图标
1.DeskTop Entry介绍 现代 Linux 桌面系统也提供了此项功能.目前,Linux KDE 和 Linux GNOME 桌面系统都使用 Desktop Entry 文件标准来描述程序启动 ...
- 假设检验的Python实现
结合假设检验的理论知识,本文使用Python对实际数据进行假设检验. 导入测试数据 从线上下载测试数据文件,数据链接:https://pan.baidu.com/s/1t4SKF6U2yyjT365F ...
- # 曹工说Spring Boot源码(10)-- Spring解析xml文件,到底从中得到了什么(context:annotation-config 解析)
写在前面的话 相关背景及资源: 曹工说Spring Boot源码(1)-- Bean Definition到底是什么,附spring思维导图分享 曹工说Spring Boot源码(2)-- Bean ...
- Friday the Thirteenth 黑色星期五 USACO 模拟 超级简单做法
1003: 1.1.3 Friday the Thirteenth 黑色星期五 时间限制: 1 Sec 内存限制: 128 MB提交: 8 解决: 8[提交] [状态] [讨论版] [命题人:外部 ...
- typescript学习笔记(一)---基础变量类型
作为一个前端开发者,学习新技术跟紧大趋势是必不可少的.随着2019年TS的大火,我打算利用一个月的时间学习这门语言.接下来的几篇文章是我学习TS的学习笔记,其中也会掺杂一些学习心得.话不多说,先从基础 ...