关于指针与类的内存分布问题(问题思考来自《程序员面试宝典》)

详解RVO与NRVO(区别于网上常见的RVO) 一直以来对 RVO 与 NRVO 以及编译器的优化操作之间的关系都不太分得清。这一次想了两天,查看了若干资料以后,总算弄清楚了。 1.RVO(Return Value Optimization) 先来看一下维基百科上对RVO(return value optimization) 的定义: "Return value optimization, or simply RVO, is 阅读详情

引起思考的例子:

class A
{
public:
	int a_1,a_2;
	A() :a_1(1),a_2(2){}
	void func()
	{
		cout << a_1 << endl;
		cout << a_2 << endl;
	}
};

class B 
{
public:
	int b;
	B() :b(3){}
	void func()
	{
		cout << b << endl;
	}
};
int main()
{
	A a;
	B * pb = (B*)&a;
	pb->func();
	system("pause");
}

虽然是一个很有问题的代码,但是需要预测此时程序的行为。程序将输出 : 1

解释:

将 &a 强制类型转换为 B* ,因此在调用 pb->func() 的时候,编译器根据 pb 的类型将调用 B::func()。A中有两个成员 a_1,a_2。在A的内存布局中,其偏移量分别为0 与 1。B中有一个成员 b 。在 B 的内存布局中,其偏移量为 0。 在  B::func() 中,对 b 的访问在汇编语言层而言是对于相对 this 指针所指地址的偏移量为 0 的 int 成员的访问。此函数调用在编译时期不会出错。而在运行时期,通过对 this 指针所指对象的偏移量为 0 的地址访问。导致此时访问到了 A 中的 a_1 成员,该对象的此时的值为 1。因此输出为 1。

但是这种问题其实没什么好纠结的。是视编译器而定的内存布局。假设编译器对 A 对象的布局中,让 a_2 位于 a_1 的前方,则测试输出的值则为 2 了。测试一个真实的样例:让 A中有虚函数,则此时  A 的内存布局中有 vptr,会使得上述分析失效。如:

class A
{
public:
	int a_1,a_2;
	A() :a_1(1),a_2(2){}
	void func()
	{
		cout << a_1 << endl;
		cout << a_2 << endl;
	}
	virtual void f()     //与上述的代码完全相同,只是在A中增加了一个虚函数
	{
	}
};

此时,输出的结果为一个地址值、即为 A::Vptr 的值(在VS2013中)。说明在VS中,虚函数指针放在类对象内存布局的头部。因此对偏移量为 0 的访问则访问到了 A 的虚函数表的指针。若 B 中也有虚函数,则 A ,B 的布局相对来说是一样的。偏移量都包含了虚函数表的指针。则仍然符合上述分析。


一个思考:

类似上述问题,类中虚函数的调用也是与偏移量有关。因为虚函数在虚函数表中存储的就是偏移量。因此虚函数的调用可能会出现如下情况:

class A
{
public:
	int a_1,a_2;
	A() :a_1(1),a_2(2){}
	void func()
	{
		cout << "afunc" << endl;
		af();
	}
	virtual void af()
	{
		cout << "af" << endl;
	}
};

class B 
{
public:
	int b;
	B() :b(3){}
	void func()
	{
		cout << "bfunc" << endl;
		bf();          //对虚函数的调用转为对虚函数表中的偏移量对应的函数的调用
	}
	virtual void bf()
	{
		cout << "bf" << endl;
	}
};
int main()
{
	A a;
	B * pb = (B*)&a;
	pb->func();<span style="white-space:pre">			</span>//将会调用 B::func();
	system("pause");
}

输出为:

bfunc

af

解释:
同理。pb->func() 调用的是 B::func(),因此输出了 bfunc 。然而由于 bf() 是B中的虚函数,而虚函数的调用会转为针对 vptr 的偏移量的调用,此时偏移量为 0 。但由于此时指针实际上指向的是 A 对象,因此 vptr 是A的 vptr 。同时 A 的 vptr 的偏移量为 0 的函数为 A::af(), 因此调用了 A::af()。并输出 af


对于继承的思考:

继承的多态体现在函数成员多态。而数据成员不具有多态性

class A
{
public:
	int val;
	A() :val(1){}

	void f()
	{
		cout << "A" << endl;
		cout << val << endl;
		func();
	}
	virtual void func()
	{
		cout << "afunc" << endl;
	}
};

class B : public A
{
public:
	int val;
	B() :val(2){}
	void f()
	{
		cout << "B" << endl;
		cout << val << endl;
	}
	virtual void func()
	{
		cout << "bfunc" << endl;
	}
};


int main()
{
	B b;
	A * pa = &b;
	pa->f();
	system("pause");
}

输出为:
A

1

bfunc

解释:

非虚函数没有多态性,指针的静态类型决定了调用的函数,因此调用A::f()。而在 A::f() 中,数据成员的访问不具有多态性,因此 val 即为 A::val。因此输出 1。而虚函数 func 的调用则存在多态性,调用了 B::func();


using声明与using指示的区别(在作用域上) 在《C++ Primer》第五版十八章中详细描述了 using 指示与 Using 声明的区别,个人也想了好久。首先,明确一下 using 声明与指示 using 声明: using std::cout; using 指示: using namespace std; 1.初步展示区别 在我们通常的理解下,using 声明引入了一个名字,在声明之后的作用域中,所有用到该名字的地方,都是指的改 阅读详情

相关推荐

在C++中调用Python接口

参照如下博客可以看看写好的cpp 在G++中怎么编译: http://blog.youkuaiyun.com/taiyang1987912/article/details/44779719 参考如下博客可以看看怎么在VS的项目中配置调用python接口 http://blog.youkuaiyun.com/c_cyoxi/article/details/23978007 参考如下地址可以解决VS中没有Pyth

Virtual_Func的博客 2360

C++常见面试题(三)——虚函数表详解

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。 这里不再对虚函数的使用进行过多的描述,基类成员函数用virtual修饰时: 基类大小多4个字节(多一个指针,多出的大小和系统位数有关系),这4个字节是一个指针,指针的名字是_vfptr。这个指针 指向一个函数指...

一个小孩 1297

国央企创新负责人如何通过平台实现科研成果的快速识别与转化?.docx

科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。

C++ 虚指针、成员变量与类对象的偏移地址

本文根据对象中虚指针、成员变量、成员函数在类对象中的相对位置,分析并给出虚指针、成员变量在类对象的偏移地址分布情况及相关源码。

稚枭天卓 3515

WRF模式学习入门基础(建议收藏!!!)

本文详细介绍了WRF-ARW(Advanced Research WRF)模式的结构组成,重点讲解了前处理系统(WPS)和主模式中的各个关键程序的功能与流程。通过对geogrid、ungrib、metgrid、real和wrf等模块的逐步解析,帮助初学者全面理解数值天气预报模型在初始化和运行过程中所需的关键步骤。

2503_90234341的博客 3075

Verilog中generate for循环的5个实战技巧(附完整代码示例)

本文详细介绍了Verilog中generate for循环的5个高级实战技巧,包括参数化模块实例化、条件化生成、多维硬件结构实现等,并附有完整代码示例。这些技巧能显著提升FPGA开发效率,特别适合中级Verilog开发者学习应用。

weixin_28725949的博客 187

虚函数指针的位置

class Test{ public: int a; int b; virtual void fun() {} Test(int temp1 = 0, int temp2 = 0) { a=temp1 ; b=temp2 ; } int getA() { return a; }...

(ಥ_ಥ) (ಥ_ಥ) (ಥ_ಥ) 博客 438

别再手动巡检了!用Zabbix 5.0 + SNMPv2自动监控华为S系列交换机(附完整命令集)

本文详细介绍了如何利用Zabbix 5.0与SNMPv2实现华为S系列交换机的自动化监控,告别低效的手动巡检。通过配置SNMPv2c安全基线、核心功能启用及Zabbix Server端深度配置,构建7×24小时监控体系,实时预警接口状态、流量波动等关键指标,大幅提升运维效率与故障响应速度。

weixin_30940783的博客 628

c++对象内存布局(虚表和虚指针)

参考文章: http://blog.youkuaiyun.com/haoel/article/details/3081385 http://www.cnblogs.com/kekec/archive/2013/01/27/2822872.html 虚函数表中虚函数在虚表中出现的位置是按照类中成员申明顺序,然后子类在前,父类在后的方式。 以下所有的函数均为虚函数。 普通继承: 虚表指针存在于子类的头部

iihtd的专栏 1344

搭建AutoSAR开发环境

任何开发都需要一套开发环境,包括软硬件环境,笔者后续将介绍的是 NXP 的 S32K144 配套的开发板。接下来详细介绍怎么获取这些软硬件工具。仅供个人学习使用!

cfl927096306的博客 6110

基于STM32设计的掌上游戏机(运行NES游戏模拟器)详细开发过程

利用STM32单片机移植NES游戏模拟器,完成NES游戏开发,可以玩标准的NES游戏,已测试超级玛丽、魂斗罗之类的都可以正常运行。

4万+

vector中不能存放引用类型!!!!!!!!

关于 vector 中不能存放引用,这是一个在初始C++时候就应该知道的问题,但是我居然没注意,还好及时发现了。 《C++ primer》上说 vector 中不能存放引用的原因是:引用不支持一般意义上的赋值操作,而 vector中元素的两个要求是: 1.元素必须能赋值 2.元素必须能复制 int a = 1; int c = 2; int & b = a; b = c;

Virtual_Func的博客 1万+

在 SAP SD(销售与分销)模块中,销售合同 / 订单的抬头(Header) 和行项目(Item) 核心数据表如下,按 “核心表 + 扩展表” 分类说明,同时区分销售合同(Contract,类型 W

SAP SD模块中销售合同/订单的核心数据表分为抬头和行项目两类。核心抬头表包括VBAK(基础数据)和VBUK(状态数据),核心行项目表包括VBAP(基础数据)和VBUP(状态数据)。扩展表包含VBAKE(抬头增强)、VBAPe(行项增强)、VBEP(计划行)、VBFA(凭证流)等。销售合同(WK/WC)和订单(OR)通过VBAK-AUART字段区分,核心表结构相同。查询时需注意单据类型筛选,并通过VBELN主键关联抬头和行项数据,KNUMV关联定价条件,VBFA表追踪凭证流。

金牌架构师 1090

C++ 'include' 的两个头文件互相包含出错

头文件保护符 通常,现在写代码时都会随手在头文件中加入头文件保护符。有以下两种形式的保护符。 #ifndef ABCDE #define ABCDE //..... 内容 #endif #pragma once 两种模式的作用是相同的,都是只让头文件在解析过程中只会展开一次。 能工作的循环包含情况 今天遇到的问题是循环包含问题,也就是说如下例子 //a.h #pragma onc

Virtual_Func的博客 7931

智驾芯片新天团!NVIDIA Thor vs 高通8797 vs 蔚来神玑NX9031,2025-2026谁能称王?

应用场景推荐芯片理由高阶NOA + L4 Robotaxi千TOPS以上,满足BEV+TransformerL2+/L3级别NOAThor-U / 高通8797算力适中,功耗控制佳,生态成熟经济型城市NOAThor-Z / S性价比均衡,适合前装普及自研平台一体化NX9031华为之外最强自研整合方案📌 TOPS不再是天花板,GPU、带宽、功耗、上市时间和车企绑定都是决胜关键📌 Thor系列仍然统治高端,但国产神经元开始觉醒📌 智驾芯片的选择,从“强”走向“适合场景”

weixin_43199439的博客 2641

基于YOLOv8的水果新鲜程度检测系统设计与实现

数据采集与预处理模块:负责收集水果图像数据并进行标注和增强深度学习模型:基于YOLOv8的目标检测模型新鲜度评估模块:根据检测结果评估水果新鲜程度用户界面:提供友好的交互界面系统集成:将各模块整合为完整系统水果新鲜程度检测需要包含多种水果在不同新鲜度状态下的图像数据集。:包含5种常见水果(苹果、香蕉、橙子、葡萄、草莓)在不同新鲜度下的图像包含约8,000张图像,分为新鲜和腐烂两类AgriVision:大型农业图像数据集,包含水果检测和分类任务Fruit-360:包含120种水果的高质量图像。

YOLO 444

模板的模板参数匹配报错问题

今天写一个模板类,使用了模板的模板参数,在实例化类对象时,报错:1. 在VS2013中错误编码为:error C3201: 类模板“std::vector”的模板参数列表与模板参数“CONT”的模板参数列表不匹配 2. 在G++中错误信息为: error: type/value mismatch at argument 2 in template parameter list for ‘templ

Virtual_Func的博客 7567

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

犯过的C++错误: vector::swap()函数

在知乎上看到一个问题,问一下代码为什么错: //... vector x; //.... x.swap(vector(x)); //错误出现的地方 我的错误解答是: 因为 vector(x) 产生了一个临时对象,将该临时对象与 X 进行 swap 操作,交换了对方的三个指针(first,end,finish),指向分配的内存空间,然后再该语句结束后,临时对象被释放掉,所以此时 x

Virtual_Func的博客 6745

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

IMSL CNL 7.0.0 (x86-64)Crack

IMSL CNL 7.0.0 (x86-64)Crack intel的算法库破解文件,这是C++版本的破解文件

关于模板类中模板成员函数在类定义外部特化报错,错为声明与定义不匹配

今天写STL中 rb_tree 的时候意外的发现一个问题:模板类的模板成员函数不能在类定义外部进行特化,特化必须放在类定义内,否则将报错:error C2244: “X::test”: 无法将函数定义与现有的声明匹配 经过写如下的简单代码可以看出问题所在:(错误版本) //定义模板类X。其中拥有模板成员 test template class X { public: X(){}

Virtual_Func的博客 4446

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

SGI 计算幂次方的精简计算方法

通常,我们需要计算 X 的 n 次方的最简单的计算方法如下: int power(int x,int n) { int result = 1; while(n--) { result *= x; } return result; } 上述程序的时间复杂度为 O(n),即需要计算 n 次方,则需要执行 n 次循环。但 SGI  的 STL 中使用了一种巧妙的方法将进入循环的次数降到

Virtual_Func的博客 2716

如何禁止一个类被继承?

当我们想将一个类设置为禁止继承时该用什么方式?有两种解决方案。提示:虚继承与友元!!Way1:强行将该类的构造函数与析构函数均设为 private,并设定该类的一个友元函数来构建该类对象,并返回指针。此时继承自该类的类将无法调用该类的构造函数,因此将在编译时报错 • 缺点:将类的使用变得与普通类不兼容,不能在友元函数外定义类对象,用户定义该类必须知道友元函数 • 优点:简单易理解 Eg

Virtual_Func的博客 2296

C++中 0 与 NULL 与 nullptr之间的关系,nullptr_t 的实现

参考了网上各种资料,理清楚了 0 与 NULL 以及 nullptr 的关系。 1.从本质上  1) 0是int型的字面值常量  2) NULL 是预处理变量,定义在 cstdlib 中,其值是0  3) nullptr 是 nullptr_t 类型的字面值。 2.cstdlib 中 NULL 的定义   #ifdef __cplusplus   #define NULL 0

Virtual_Func的博客 1555

使用模板获取迭代器所指对象的类型

假设现在要求通过迭代器类型推测出迭代器所指对象的类型,在不考虑 value_type 的情况下,使用模板的参数推导机制实现要求,即: template void func(T iter) { //需要知道迭代器 iter 所指对象的类型,注:不是T } 可以通过加一层函数来实现,将当前函数当做通用接口,即: template void func(T iter)

Virtual_Func的博客 1465

指针值加上一个数值的实际意义

一直没怎么注意指针值加上数值的问题,认为就是在指针地址上往后叠加n个字节,直到今天写STL 库中二级空间配置器,内存报错:堆被损坏!单步跟踪后才发现问题首先说明问题:int a(0); //假设 &a = 0x1002 int * p = &a; //此时 p = 0x1002 p = p + 3; //错误想法: p = 0x1002 + 3 = 0x1005//但该操作的实际

Virtual_Func的博客 1439

sizeif(i++).sizeof(++i) 的问题

考虑以下代码:int i = 3; int j = sizeof(++i); int k = sizeof(i++); cout << i << endl; 对上述代码,输出的结果为 : 3原因:根据C99规范, sizeof是一个编译时刻就起效果的运算符,在其内的任何运算都没有意义, k= sizeof(i++); 在编译的时候被翻译成 j=sizeof((i++的数据类型)) 也就是 j = s

Virtual_Func的博客 1248

仿函数存在的原因

我们知道仿函数是仅重载了括号操作符,且定义了若干相应型别的类。仿函数存在的意义,是为了能提取出仿函数中定义的相应型别,使仿函数能与函数适配器进行配接,而这些相应型别则主要是用来表现函数参数类型与传回值类型。 函数指针因为没有相应型别而不能与STL 中别的组件进行搭配。因此,仿函数的相应型别是一个很重要的因子。 STL 中定义了两个类,分别代表一元仿函数和二元仿函数,其中没有任何数据成员或函数成

Virtual_Func的博客 1169
上一篇: 字符串匹配的KMP算法
下一篇: BP神经网络与RBF神经网络讲得好的博客链接
Virtual_Func
博客等级 码龄11年 38粉丝 113原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值