C++静态联编和动态联编

news2024/11/17 14:43:22

目录

2.1静态联编

2.2动态联编

2.3虚函数面试题

2.3.1构造函数中使用memset函数

2.3.2this指针与虚函数的调用

2.3.3构造析构函数中调用虚函数

2.3.4动态和静态联编与访问属性和默认值

2.3.5动态创建对象时的析构函数


联编是指计算机程序彼此关联的过程,是把一个标识符名和一个存储地址联系在一起的过程,也就是把函数的调用和函数的入口地址相结合的过程。

2.1静态联编

静态联编(早期绑定):静态联编是指在编译和链接阶段,就将函数实现和函数调用关联起来。

C语言中,所有的联编都是静态联编,并且任何一种编译器都支持静态联编。C++语言中,函数重载和函数模板也是静态联编;使用对象名加点”."成员选择运算符,去调用对象虚函数,则被调用的虚函数是在编译和链接时确定。(称为静态联编)。

2.2动态联编

动态联编亦称滞后联编或晚期绑定: 动联编是指在程序执行的时候才将函数实现和函数调用关联起来。

C++语言中,使用类类型的引用或指针调用虚函数(成员选择符”->"),则程序在运行时选择虚函数的过程,称为动态联编。

2.3虚函数面试题

2.3.1构造函数中使用memset函数

class Object
{
private:
    int value;
public:
    Object(int x=0):value(x)
    {
        memset(this,0,sizeof(Object));
    }
    void func()
    {
        cout<<"Object::func: "<<value<<endl;
    }
    virtual void add(int x)
    {
        cout<<"Object::add: "<<x<<endl;
    }
};
int main()
{
    Object obj;
    Object* op=&obj;
    obj.add(1);//执行完这句正常,打印Object::add: 1
    op->add(2);//执行这句时系统崩溃
    return 0;
}

原因:

在构建对象时执行memset函数将该对象的所有数据全部清零,包括虚表指针。

由于obj.add(1)是静态联编在编译时就已经绑定调用关系,不会查虚表,所以可以正常执行;

而op->add(2)是动态联编在运行时通过查虚表来确定调用关系,但由于该对象的虚表指针被清零变成空指针,查表时对空指针进行解引用导致系统崩溃。

2.3.2this指针与虚函数的调用

class object
{
private:
	int value; 
public:
		object(int x = 0) : value(x) {}
		void print() 
		{ 
			cout << "object::print"<< endl; 
			add(1);
		}
		virtual void add(int x)
		{
			cout << "object::add: " << x << endl; 
		}
};
class Base : public object
{
private:
	int num;
public:
	Base(int x = 0) :object(x + 10), num(x) {}
	void show()
	{
		cout << "Base::show"<< endl;
		print();
	}
	virtual void add(int x)
	{ 
		cout << "Base::add: " << x << endl; 
	}
};
int main()
{
	Base base; 
	base.show();
	return 0;
}

结果:

 

原因:

base对象调用show()方法,先打印Base::show,再通过Base类型的this调用print()方法,公有继承的派生类对象能够调用基类的非私有方法。

进入print函数,其this指针为Object类型,被Base类型的this指针所赋值,即this指针指向base对象,打印Object::print。

通过this指针调用add(1)函数,此时this指针是指向base对象的Object类型指针,由于add()函数是虚函数,并且使用指针进行调用,所以要查表,且查的是Base类型的虚表,形成动态联编,调用Base类型的add方法,打印Base::add:1。

空指针能调用函数体内不对this指针解引用的函数:

class Object
{
    int value;
    public:
    Object(int x=0):value(x){}
    void func(int a=10)
    {
        cout<<"Object::func:a:"<<a<<" value:"<<value<<endl;
    }
    void print()const
    {
        cout<<"print"<<endl;
    }
};
int main()
{
    Object* op=nullptr;
    op->print();//可以运行成功,打印print
    op->func();//可以编译通过,但运行崩溃。
    return 0;
}

虽然op指针为空指针且print()函数中的this指针被op所赋值也为空指针,但是能够调用print()函数的原因是使用op指针调用print()函数和执行print()函数的过程中没有对op指针和this指针进行解引用,所以就不会崩溃掉。

调用func()函数崩溃的原因是在func()函数中打印value的值对为空的this指针进行了解引用,导致崩溃。

2.3.3构造析构函数中调用虚函数

class object
{
private:
	int value;
public:
	object(int x = 0) :value(x)
	{
		cout << "Create object: "<< endl;
		add(12);
	}
	~object()
	{
		cout << "Destory object" << endl;
		add(23);
	}
	virtual void add(int x)
	{
		cout << "object::add: " << x << endl;
	}
};
class Base : public object
{
private:
	int num;
public:
	Base(int x = 0) :object(x + 10), num(x)
	{
		cout << "Create Base "<< endl;
		add(1);
	}
	~Base()
	{
		cout << "Destroy Base" << endl;
		add(2);
	}
	virtual void add(int x)
	{
		cout << "Base::add: " << x << endl;
	}
};
int main()
{
	Base base;
	return 0;
}

结果:

 

原因:

首先创建对象base时调用Base构造函数但不进入,由于Base继承于Object,所以要去调用Object构造函数,在设置Object类成员属性前设置虚表指针指向Object类的虚表,然后设置value值,再进入Object类构造函数体,打印Create object:。

执行add(12),add()函数是虚函数,用this指针调用需要查虚表,但此时虚表指针指向Object类的虚表,所以调用Obejct类的add()方法,打印object::add:12。

再回到Base类构造函数,首先设置虚表指针指向Base类的虚表,设置num值,进入函数体打印Create object:。

执行add(1)要查虚表,但此时虚表为Base类的,所以调用Base类的add()函数,打印Base::add:1。

销毁base对象时调用Base类的析构函数,在进入函数体之前先重置虚表指针指向Base类虚表,然后打印Destroy Base。

执行add(2)要查虚表,此时虚表指针指向Base类的虚表,所以调用Base类的add()函数打印Base::add:2。

再调用Object类的析构函数,同样在进入函数体之前先重置虚表指针指向Object类的虚表,然后打印Destroy object。

执行add(23)要查虚表,此时虚表指向object类的虚表,所以调用Object类的add()函数打印object::add:23。

总结:

在构造函数、拷贝构造函数或者析构函数中调用虚函数,一定是调用本类的虚函数,如果本类没有重写虚函数,则调用基类的虚函数。在构造、拷贝构造、析构函数中调用虚函数时编译器一律采用静态联编的方式,而不使用动态联编。

2.3.4动态和静态联编与访问属性和默认值

class object
{
public:
	virtual void func(int a = 10)
	{
		cout << "object::func: a " << a << endl;
	}
};
class Base : public object
{
private://只在编译时有作用,对于运行时没有作用
	virtual void func(int b = 20)
	/*虚函数的动态绑定是在运行时根据对象的实际类型来确定的,而默认参数值的解析则在编译时完成。
	因此,在动态绑定时,默认参数值已经确定,无法再根据实际对象类型去修改。*/
	{
		cout << "Base::func: b " << b << endl;
	}
};
int main()
{
	Base base;
	object* op = &base;
	op->func();
    //base.func();//error,编译不通过
	return 0;
}

结果:

 

问题点1:Base类的func方法为私有访问属性,为什么能在外部函数中调用。

问题点2:调用Base类的func方法时,其默认值为20,为什么打印结果为Object类的func的默认值10。

原因:

首先程序要进行编译,要识别类的成员属性类型、成员函数名、返回类型、参数列表、默认值以及其可访问属性。在编译时确定调用关系Base类的func()方法为私有,使用base对象调用无法编译通过;

op->func()可以编译通过,因为op是object类的指针,其func()函数的可访问属性为公有,所以能够编译通过。

且编译时确定该方法的默认值为object类的func方法的默认值10,因为默认值是在编译时与调用者的类型绑定。

但由于func()为虚方法,在调用时是按照动态联编的方式调用,实际调用的是Base类的func()方法,但默认值与调用者的类型绑定为10。

即便Base类的func()方法为私有,但动态联编不会去考虑可访问属性,因为可访问属性是只在编译期确定的,编译完成后就不会考虑可访问属性。

2.3.5动态创建对象时的析构函数

class object
{
private:
	int value;
public:
	object(int x = 0) :value(x)
	{
		cout << "Create object: "<< endl;
	}
	~object()
	{
		cout << "Destory object" << endl;
	}
	virtual void add(int x)
	{
		cout << "object::add: " << x << endl;
	}
};
class Base : public object
{
private:
	int num;
public:
	Base(int x = 0) :object(x + 10), num(x)
	{
		cout << "Create Base "<< endl;
	}
	~Base()
	{
		cout << "Destroy Base" << endl;
	}
	virtual void add(int x)
	{
		cout << "Base::add: " << x << endl;
	}
};
int main()
{
	object* op = new Base(1);
	op->add(1);
	delete op;
    return 0;
}

结果:

 

问题:构造的对象为Base类型,在程序结束时要销毁对象,应该要调用基类的析构和派生类的构造函数,但现在只调用基类的构造函数。

原因:

对于delete来说,op的类型为object类型的指针,所以它只会调用object类的析构函数。

如果在Base类中动态申请空间,在其析构函数中释放该空间,现在没有调用Base类的析构函数,这样就会存在内存泄漏。

或者如果在Base类构造函数中打开文件,在析构函数中关闭文件,但没有调用析构函数,这样就会造成文件无法关闭。

解决办法:将析构函数设计为虚函数

动态创建对象时,需要将析构函数也要设计为虚函数,这样delete时就会查虚表,从而调用派生类的析构函数,调用完派生类的析构函数后会再回到基类中,调用基类的析构函数。

正确的结果:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/654971.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Fiddler汉化成功

我安装的fiddler 操作系统是&#xff1a;Win10 64Bit 操作系统的版本号是&#xff1a;v5.0.20194.41348 for .NET 4.6.1 fiddler下载地址&#xff1a; 我用夸克网盘分享了「02-Web调试工具-FiddlerSetup.exe」&#xff0c;点击链接即可保存。 链接&#xff1a;https://pan.quar…

什么是项目里程碑?如何为项目成功设置?

高速公路上每隔一公里就有一个标志牌&#xff0c;这表明你需要进一步行驶才能到达目的地的距离。虽然没有这些标志你也可以到达目的地&#xff0c;但它们的存在使你放心&#xff0c;让你确信走在正确的道路上。 项目里程碑在项目管理中也有同样的作用。当你的项目实现目的时&a…

Linux内核学习----整体概览

目录 1、概述 2、核心抽象及设计选型 2.1. 对进程和内核的抽象 2.2. 对进程地址空间的抽象 2.3. 支持可重入可抢占的内核 2.4. 放松管控与努力回收 2.5. 单块结构内核动态加载模块 2.6. 为系统中的一切活动打拍子 2.7. 一切皆文件的理念 3、Linux整体架构模块说明 3.…

对C++中const的说明

对C中const的说明 在C中&#xff0c;const是一个关键字&#xff0c;用于指定对象或变量是只读的&#xff0c;即不可修改。它可以应用于不同的上下文中&#xff0c;包括&#xff1a; 对象和变量声明&#xff1a;通过在变量或对象的声明前加上const关键字&#xff0c;可以将其标…

ACL2022 Document-Level Event Argument Extraction via Optimal Transport

Document-Level Event Argument Extraction via Optimal Transport 论文&#xff1a;https://aclanthology.org/2022.findings-acl.130/ 代码&#xff1a;- 期刊/会议&#xff1a;ACL 2022 摘要 事件论元抽取&#xff08;EAE&#xff09;是事件抽取的子任务之一&#xff0c…

智驾传感器新风向!拐点将至

“大家都比较关注激光雷达&#xff0c;尤其是在今年整个行业聚焦降本的大背景下&#xff0c;这个赛道还行不行&#xff1f;”6月8日&#xff0c;2023年度&#xff08;第十四届&#xff09;高工智能汽车开发者大会上&#xff0c;高工智能汽车研究院首发《2023-2025年中国汽车市场…

【备战秋招】每日一题:4月29日美团春招第三题:题面+题目思路 + C++/python/js/Go/java带注释

为了更好的阅读体检&#xff0c;为了更好的阅读体检&#xff0c;&#xff0c;可以查看我的算法学习博客第三题-酒王 在线评测链接:P1268 题目内容 塔子哥和他的朋友们共 n 人是一群热爱生活的年轻人&#xff0c;他们经常在一起吃饭&#xff0c;聊天&#xff0c;玩游戏。有一…

预设Preset简单使用

作用 是一个资源文件。可以保存组件、资源、项目设置的属性&#xff0c;将属性应用到组件、资源、项目设置上。 例如&#xff0c;创建一个Transform预设&#xff0c;可记录Transform的属性&#xff0c;其他Transform应用预设&#xff0c;会使用预设中的数据。 文档 预设 预设…

【Vue全家桶高仿小米商城】——(二)Git安装与配置

文章目录 第二章&#xff1a;Git安装和配置一、Windows/Mac/Linux安装二、环境变量配置、开发工具配置Windows - 环境变量Mac/Linux - 环境变量VSCode配置Git使用 VScode git&#xff0c;提交到仓库 三、存储密码 - SSH添加秘钥Git配置命令遇到的问题 四、Git常用命令 第二章&…

新手怎么注册速卖通及其流程?图文详解版不信你还不会!

龙哥发现最近讨论速卖通的人还挺多的&#xff0c;今天龙哥就给大家讲一下新手注册速卖通的流程&#xff0c;特别是需要你提前准备好的资料。感兴趣的朋友接着往下看吧&#xff01; 速卖通店铺注册条件 1、营业执照、商标 速卖通要求注册商家必须具备合法的企业身份或个体工商户…

浅谈职场中的工作失误

浅谈职场中的工作失误 关于职场中的工作失误如何处理失误一点感言 笔者在一家软件公司从事传统的数据运维工作&#xff0c;也有十年之久了。十年的数据运维工作&#xff0c;真是一步一个脚印&#xff0c;一步一个坑踩出来的&#xff0c;也没想到这一干就是十年… 关于职场中的…

SeqTrack: Sequence to Sequence Learning for Visual Object Tracking

摘要 在本文中&#xff0c;我们提出了一种新的序列到序列学习框架的视觉跟踪&#xff0c;称为SeqTrack。它将视觉跟踪转换为一个序列生成问题&#xff0c;它以自回归的方式预测对象边界盒。这与之前的Siamese跟踪器和transformer跟踪器不同&#xff0c;它们依赖于设计复杂的磁…

【活动访谈】发力数字基座 推动物联创新—航天科技控股集团AIRIOT4.0平台发布会活动专访

近日&#xff0c;由航天科技控股集团股份有限公司主办的“数字基座 智慧物联—AIRIOT4.0平台发布会”在北京圆满落幕。航天三院科技委总工程师王连宝应邀出席本次会议并接受媒体采访&#xff0c;共同参与访谈的还有AIRIOT产品研发创始人、航天科技控股集团股份有限公司智慧物联…

python生成日报

目录 一&#xff1a;日报生成工具二&#xff1a;日报工具使用方式三&#xff1a;最终日报生成展示 一&#xff1a;日报生成工具 #!/usr/bin/python # coding:utf8class GetHtml(object):def __init__(self):self._html_head """<html><body style&qu…

美颜滤镜SDK在实时视频应用中的应用

随着智能手机的普及和网络带宽的增强&#xff0c;实时视频应用已经成为了人们日常生活中不可或缺的一部分。而在实时视频应用中&#xff0c;美颜滤镜SDK的应用也越来越广泛。本文将介绍美颜滤镜SDK在实时视频应用中的应用。 一、美颜滤镜SDK的概念 美颜滤镜SDK是一种软件开发工…

Jetpack Compose — 让Composable具备生命周期感知

Jetpack Compose — 让Composable具备生命周期感知 我们将研究不同的方法来实现可组合&#xff08;Composable&#xff09;的生命周期感知。我们还将了解可组合生命周期和视图&#xff08;View&#xff09;生命周期之间的区别。 我们将逐步探索不同的解决方案&#xff0c;以寻…

C51/C52单片机,最小系统

一个小白&#xff0c;开始学习单片机&#xff0c;从C51/52开始&#xff0c; 我学习的型号是STC98C52单片机。 STC89C52是一种低功耗、高性能CMOS8位微控制器&#xff0c;具有8K在系统可编程Flash存储器。在单芯片上&#xff0c;拥有灵巧的8位CPU和在系统可编程Flash&#xff0…

NeRF系列(4):Ha-NeRF: Hallucinated Neural Radiance Fields in the Wild论文解读

主页&#xff1a; 主页&#xff1a;Ha-NeRF&#x1f606;: Hallucinated Neural Radiance Fields in the Wildhttps://rover-xingyu.github.io/Ha-NeRF/论文&#xff1a;https://openaccess.thecvf.com/content/CVPR2022/papers/Chen_Hallucinated_Neural_Radiance_Fields_in_…

常用API(Object,Objects,StringBuilder,Math,System,BigDecimal)

1&#xff1a;Object类 1&#xff1a;Object类的作用&#xff1a; 一个类要么默认继承Object类&#xff0c;要么间接继承了Object类&#xff0c;Object类是Java中的祖宗类。Object类的方法是一切子类都可以直接使用的&#xff0c;所以我们要学习Object类的方法。 2&#xff…

容器(第八篇)ansible-模块

ansible是什么&#xff1f; Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部署、…