c++ gdb 绑定源码_第18篇:C++ 静态绑定和动态绑定
编译程序时,C ++编译器对源代码中的每个语句转换为一行或多行机器语言。 我们从之前的汇编基础系列和内存管理文章中,已经知道每行机器语言都有其自己的唯一的顺序地址。 由于函数也是一个数据对象,它也将转换为机器语言并提供下一个可用地址。因此每个功能都以唯一的地址结尾。
如何理解绑定
绑定(Binding)是指将变量和函数名转换为地址的过程。
早期绑定(Early binding):绝大部分的顺序执行逻辑中函数调用或某个确定数据类型的(不存在选择性)的类类型的对象对成员调用都属于早期绑定
#include class Employee{
....
};
class Teamer:public Employee{
public:
size_t d_members;
std::string d_name;
Teamer(const std::string &name,size_t memeber)
:d_name(name),d_members(memeber){}
....
void show(){
std::cout<
std::cout<
}
size_t get_members(){
return d_member;
}
};
调用代码示例
int main(void){
//早期绑定 Teamer tm=Teamer{"胜利队",52};
//早期绑定 tm.get_memeber();
//早期绑定 tm.show();
}
早期绑定意味着绑定的函数或者变量,已经在编译阶段,该语句已经被编译成“call 函数地址”或"callq 函数地址"这样的汇编指令格式(如下图所示),并且这些汇编指令中的函数地址在程序编译后是固定不变的,请记住,所有函数都有唯一的地址。 因此,当编译器(或链接器)遇到函数调用时,它将用机器语言指令替换该函数调用,该指令告诉CPU跳转到该函数的地址,因此早期绑定也叫静态绑定。
动态绑定
在一些带有决策性的业务逻辑的代码中,要等到用户的反馈(通常是条件判断/参数类型判定...),直到运行时,根据决策的结果才能知道将调用哪个函数。这称为后期绑定(或动态绑定),动态绑定的技术的本源就是函数指针(也可以称为函数原型)。在C ++中运行时多态正是使用的就是函数指针。
为了简化话题的导入,我们不妨先从面向过程的动态绑定说起。这也是当作复习函数指针的另类方式。
函数指针是一种指向函数而不是变量的指针,可以通过使用指针上的函数调用运算符"()"或“(type1,type2,....)”的运算符号形式来调用函数指针指向的函数。
函数指针回顾
下面示例中5个用于计算相见多边形面积的简易函数,他们非常适合用于展示函数指针的用法,示例代码中开头全局声明了三个函数指针,分别对应如下函数原型
以下函数原型仅接受一个传入参数,匹配double (*one_pars_func)(double)double square(double)
double circle(double)
以下函数原型匹配double (*two_pars_func)(double,double),接受两个传入参数;double retangle(double,double)
以下函数原型匹配double (*thr_pars_func)(double,double,double)接受三个传入参数double triangle(double,double,double)
double trapezoid(double,double,double)
#include #include #include #include #include
double (*one_pars_func)(double);
double (*two_pars_func)(double,double);
double (*thr_pars_func)(double,double,double);
double square(double x){
return x*x;
}
double circle(double x){
return 3.14*x*x;
}
double triangle(double a,double b,double c){
if(a<0 || b<0 || c<0 || a+b<=c || b+c<=a || a+c<=b ){
std::cout<
return -1;
}
float k=(a+b+c)/2.0;
return sqrt(k*(k-a)*(k-b)*(k-c));
}
double trapezoid(double a,double b,double h){
return (a+b)*h/2.0;
}
double retangle(double a,double b){
return a*b;
}
//辅助函数- 提取参数double get_data(std::deque &q){
double tmp=0.0;
if(!q.empty()){
tmp=q.front();
q.pop_front();
return tmp;
}
}
上面的示例代码中的辅助函数get_data仅仅将从deque容器弹出参数,提供后期函数指针被实际调用时使用。
下面的示例代码是根据调用层的决策逻辑(main函数的业务逻辑)函数的,而有选择性地初始化我们前面声明的三个函数指针。示例中的calc_area函数有三个参数shapeType 是接收来自用户输入的需要计算多边形的类型的决策参数。整数1表示用户需要计算正方形的面积 ,会将square函数地址赋值给one_pars_func函数指针。
整数2表示用户计算圆形的面积,会将circle函数地址赋值给two_pars_func函数指针。
整数3表示计算矩形面积,会将retangle函数地址赋值给two_pars_func函数指针。
整数4表示计算三角形面积,会将triangle函数地址赋值给thr_pars_func函数指针。
整数5表示计算梯形面积,会将triangle函数地址赋值给thr_pars_func函数指针。
通过用户输入的选项从而动态地使函数指针指向某个指定具体的函数地址,这个动作其实就是“绑定”,但需要注意的是我们还没有调用该函数,这个阶段仅仅持有该被指向的函数地址:
double calc_area(int shapeType,
std::deque &q,
std::string &shapeName){
switch(shapeType){
case 1:
one_pars_func=square;
shapeName="正方形";
break;
case 2:
one_pars_func=circle;
shapeName="圆形";
break;
case 3:
two_pars_func=retangle;
shapeName="长方形";
break;
case 4:
thr_pars_func=triangle;
shapeName="三角形";
break;
case 5:
thr_pars_func=trapezoid;
shapeName="梯形";
break;
}
//根据参数的个数,判定调用那个函数原型 size_t length=q.size();
double result;
switch(length){
case 1:
result=one_pars_func(get_data(q));
break;
case 2:
result=two_pars_func(
get_data(q),
get_data(q)
);
break;
case 3:
result=thr_pars_func(
get_data(q),
get_data(q),
get_data(q)
);
break;
default:
return result;
}
}
上面示例代码的第二个switch/case控制结构,通过用户的参数个数判断调用那个函数指针,此时通过函数指针的特殊操作符"()",调用函数也称为间接函数调用.
main调用代码,我就不废唇舌,本来这个示例就要求读者对C++有中级程度的基础。
std::string prompt(const std::string &info){
std::string tmp;
std::cout<
getline(std::cin,tmp);
return tmp;
}
int main(void){
std::string dg1,dg2,shapeName;
int choice;
std::deque params;
double result;
std::cout<
std::string tip1="1-正方形,2-圆形,3-长方形
,4-三角形,5-梯形";
std::string tip2="请输入计算形状面积
的参数(例如:a,b,c 以逗号隔开)";
choice=stoi(prompt(tip1));
do{
dg1=prompt(tip2);
std::stringstream istr(dg1);
//将istream读取提取子字符串并转换为double类型的数字 std::size_t offset=0;
double tmp;
while(getline(istr,dg2,',')){
tmp=stod(dg2,&offset);
params.push_back(tmp);
}
//计算形状面积 result=calc_area(choice,params,shapeName);
std::cout<
choice=stoi(prompt("需要继续吗?(-1表示退出程序)"));
if(choice>0){
std::cout<
choice=stoi(prompt(tip1));
}
}while(choice>=1 && choice<=5);
return 0;
}
在此示例中,我们通过用户的输入的选项,将声明函数指针指向要调用的函数,而不是直接调用这些具体的函数。 然后我们通过参数个数决定这些函数指针调用哪些函数。 编译器无法使用早期绑定来解析类似
two_pars_func(x,y),thr_pars_func(x,y,z),这样的函数调用。因为它无法告诉函数指针在编译时将指向哪个函数 !你不相信我的说法吗? 可以去验证哈!
我们之前讨论静态绑定的时候,已经讨论过其实际上在编译时在汇编阶段在32位环境中会转换为call xxxx,在x86_64环境中会被转换为callq xxxx这样形式的指令。
反证我的说法
假设你在gdb命令可以使用disas指令反编译我们示例中calc_area函数得到反汇编代码 ,你是无法找到callq 0x401384或callq 0x40135d这类指令的,例如我写本文时,在我的机器打印的这些示例函数的地址如下。
动态绑定的性能问题
后期绑定的效率稍低,因为它涉及额外的间接访问。 通过早期绑定CPU可以直接跳转到函数的地址。 对于后期绑定,因为涉及CPU和寄存器存储/加载一系列指令,程序必须读取指针中保存的地址,然后跳转到该地址。使其速度稍慢。 后期绑定的优点是它比早期绑定更灵活,因为不需要在运行时就决定要调用什么函数。
这也是一些动态语言的卖点,例如需要使用Python程序猿在编写代码无需考虑变量和参数的类型,因为Python的解析器的底层就是用到了运行时的一系列类型检测和类型检测后的内存分配以及C的函数指针的间接调用等技术完成了对Python代码的解析和资源初始化,这一切是以低性能为代价的。而Cython的其中一项技术就是静态绑定,即是将一些程序员认为非常慢的Python业务逻辑转换可能存在大量局部变量和参数的上下文,为这些局部变量和参数指定C/C++的数据类型,实质上这些通过Cython语法修饰的变量和参数已经绕过Python解析器的类型检测,直接以C级别的静态绑定来运行。因此就从某种程度弥补了Python的性能底下的诟病。
小结
我们后续文章会继续循序渐进深入探讨一下C++面向对象版本的动态绑定话题。其实C++中动态绑定的技术牵涉好几个知识区块,很多谈及C++面向对象多态的时,尤其是运行时多态即动态绑定,到目前为止,绝大部分的牵涉动态绑定话题的文章,不是先抛出几个已经被用烂的虚函数示例代码(不是B继承A,就是D1继承D2...),然后之后千篇一律地给出结论般C风格的表达式(*(p->vptr)[n])(p),结论没错!但我想说这样抄来抄去有意思吗!?中间的牵涉的知识链条和内存状态只字不提就能够显示自己水平多么高逼格有多高~!?,老子表示对这种现象感到灰常厌恶.
因此,我这里就在自己的简书主页特意开个《C++ 多态》的文集,用来对以往的知识做个梳理和总结。虚指针和虚表,虚函数
函数指针和间接调用
类型转换:这个知识区块包括用户自定义类型的向上转换和向下转换
更加复杂函数指针间接调用。
读后有收获可以微信或支付宝请笔者喝杯咖啡
c++ gdb 绑定源码_第18篇:C++ 静态绑定和动态绑定相关推荐
- c++ gdb 绑定源码_【Vue原理】VNode 源码版
↑点击上方 "神仙朱" 一起研究Vue源码吧 专注 Vue 源码分享,文章分为白话版和 源码版,白话版助于理解工作原理,源码版助于了解内部详情,让我们一起学习吧 研究基于 Vue版 ...
- c++ gdb 绑定源码_2020重学Go系列:30. 非常详细的 Go 语言 GDB 调试指南
做为新手,熟练掌握一个好的调试工具,对于我们学习语言或者排查问题的时候,非常有帮助. 你如果使用 VS Code 或者 Goland ,可以直接上手,我就不再写这方面的文章了. 其实相比有用户界面的 ...
- 手机应用软件下载导航php源码_第八篇:你需要一个没有广告的导航吗,我免费给你源码...
这几天电脑重装了系统,安装了我常用的浏览器谷歌和QQ浏览器,还有自带的Microsoft Edge,但是打开电脑,导航主页被锁定成了QQ导航,我倒是不反感用哪个导航产品,就是这种持续的广告真的是很浪费 ...
- java.lang.object源码_第三篇:java.lang.Object 类源码分析
Object所包含的方法如下: ① public Object(); 构造函数: 大部分情况下,类对象的声明,都是通过构造函数完成的(Java中规定:在类定义过程中,对于未定义构造函数的类,默认会有一 ...
- php ssc 源码_吃透这篇,你也能搭建出一个高并发和高性能的系统
什么是高并发?高并发是互联网分布式系统架构的性能指标之一,它通常是指单位时间内系统能够同时处理的请求数,简单点说,就是 QPS(Queries Per Second). 那么我们在谈论高并发的时候,究 ...
- 分享一款超多功能工具箱组合微信小程序源码_支持流量主,无需服务器和域名!适合小白
分享一款超多功能工具箱组合微信小程序源码_支持流量主,无需服务器和域名!适合小白操作! 简介: 超多功能工具箱组合微信小程序功能实用性质特别的高,用户还能覆盖的广一些具体功能列表如下: 1.证件照制作 ...
- 分享一款超40款多功能工具箱组合微信小程序源码_支持流量主,聚集市面上大部分功能的小程序,无需服务器和域名!源码拿去!
分享一款超多功能工具箱组合微信小程序源码_支持流量主,无需服务器和域名!适合小白操作! 简介: 超多功能工具箱组合微信小程序功能实用性质特别的高,用户还能覆盖的广一些具体功能列表如下: 1.证件照制作 ...
- 宝宝起名神器小程序源码_支持多种流量主模式
2022年马上到了,还不知道怎么给虎宝宝取名字么? 那么这款小程序源码就可以帮到你了,这款小程序支持输入姓氏自动起名. 不满意还可以点击换一换来找到满意的,支持起两个字或者三个字的名字. 另外也给该款 ...
- 新动态视频壁纸微信小程序源码_支持多种分类短视频-也有静态壁纸
这是一款主打动态视频壁纸的一款微信小程序源码,当然啦,里面也是有静态壁纸的. 其实这款小程序也可以说是短视频小程序都可以,该款小程序全采集,另外支持多种流量主!! 下载链接: 新动态视频壁纸微信小程序 ...
最新文章
- sql 怎样 得到 的客户端的ip地址_怎样用卷发棒?正确用法大揭密-装修攻略
- 32 usb 配置描述符_USB协议详解第4讲(USB描述符标准配置描述符)
- PHP中redis的使用
- [java]键盘录入数值到数组-根据数值获取角标-找出数据中最大数值-反转数组
- 一篇详文带你入门 Redis
- Linux C++线程池
- Association, Composition and Aggregation in UI5, CRM, S/4HANA and C4C
- liunx宝塔配置https_宝塔面板安装教程
- 百度,淘宝,腾讯三大巨头HTML页面规范分解
- 基于JAVA+SpringMVC+Mybatis+MYSQL的停车预约管理系统
- ref转发到DOM元素
- android 中如何监听按键的长按事件
- 美通社:2018年全球企业品牌影响力调查报告
- 悲剧的购物经历(附:最好不要买响尾蛇3G)
- 《iVX 高仿美团APP制作移动端完整项目》05 美食页商家推荐内容、分类、推荐商家制作
- 打工妹变身董事长,是早有预谋还是认知使然——解密《天道》肖亚文逆袭之路
- 宇视摄像机巡航和自动跟踪哪个优先级高?
- pip安装指定keras版本
- 三相电网的共模与差模
- java记事本编译_Java记事本编译