代码先锋网 代码片段及技术文章聚合

C++-----深入探索对象模型-Data语意学(二)

1、Nonstatic data member在类对象中的排列顺序和声明顺序一样,任何中间介入的static data member都不会被放进对象布局中。

2、同一个access section(也就是private、public、protected区段中),member的排列只需要符合较晚出现的member在类对象中有更高的地址。(从低到高排列)各个member不一定连续排列。有可能会有东西介于被声明的member之间,member的边界调整可能就需要填补一些字节。

3、当一个类有虚拟机制时,对象中会有vptr,不同的编译器可能会把vptr放在对象的最前面或者最后面,常见的是最前面。

4、目前各家的编译器会把一个以上的access section(也就是一个类里有多个public、private、protected)连锁在一起,依照声明的顺序成一个连续的区块,access section的多少并不会带来额外的负担,和在一起声明是一样的。

5、对于静态数据成员,由于其在程序中只有一份实例,并且和对象无关,使用对象或对象指针或者类来存取并没有太大的差异,如果取一个静态成员的地址,得到的是一个指向其数据类型的指针,而不是一个指向其类成员的指针,因为静态成员并不在一个类对象里。

6、如果两个类有相同名称的静态成员,当其存于data segment(数据段)时,会出现命名冲突,编译器解决的方法是暗中对每一个static data member编码(name_mangling),这样就可以获得独一无二的程序识别代码。

7、Nonstatic data member直接存在每一个class object之中,必须经由类对象来存取,事实上成员函数中的数据的直接存取都是经过隐式的类对象(this指针)完成的。

8、想对一个非静态成员进行存取,编译器会把类对象的起始地址加上一定的data member 偏移位置。

class A{
public:
    float x;
};

A a;
a.x=0.0;
//
&a.x=&a+(&A::x-1);

    注意这个-1操作,指向数据成员的指针,其偏移量总是被加上1,这样可以使编译系统区分出一个指向数据成员的指针,用以指出类的第一个成员和一个指向数据成员的指针,没有指出任何成员的两种情况。

9、通过对象指针还是对象来存取数据成员之间的差异:

    当类是一个派生类时,其继承链中标有一个虚基类存在,并且存取的成员是一个从该虚基类中继承来的成员时,就会有重大差异,此时如果通过指针来存取的话,由于多态机制的存在,指针所绑定的对象类型要到执行期才能确定,所以存取的操作要到执行期,但是如果用对象来存取就不会有这样的问题,成员的偏移量在编译期就能确定。

10、关于对象如何通过偏移量来访问成员,参考下面程序

#include<vector>
#include<iostream>
using namespace std;
class Base {
public:
	Base() {
	};
	
public:
	int a;
	int b;

};

typedef void(*Pfun)();
int main() {
	Base base_test;
    //获得成员对于类的偏移量
	int Base::*ptr = &Base::a;
	int Base::*ptr2 = &Base::b;
	//获得该对象成员的地址
	int *a = &(base_test.a);
	int *b = &(base_test.b);
	//输出地址
	cout << a << endl;
	cout << b << endl;
    //输出偏移量
	printf("%p\n", ptr);
	printf("%p\n", ptr2);
   /*应该是ostream对象没有重载类成员指针的参数,故不能直接输出类成员指针的类型,而我们知道指针类 
   **型与bool类型的转换属于标准转换的(常常用来测试指针合法性是否为空),而ostream对象可以输出 
   **bool类型,故编译器将成员指针类型转换成了bool类型,从而输出,既然这样为什么全是输出1呢?说明 
   **地址全是合法的,即偏移量全是大于0,不对呀,第一个类成员的偏移量不是0么,因为指向数据成员的指 
   **针偏移量总是会加上1*/
	cout << ptr << endl;
	cout << ptr2 << endl;

	
	return 0;
}

    所以在这里经常会遇到一些笔试题,比如下面这道题

#include <stdio.h>
 
class A
{
public:
    A() {m_a = 1; m_b = 2;}
    ~A() {}
    void fun() {printf("%d %d", m_a, m_b);}
private:
    int m_a;
    int m_b;
};
 
class B
{
public:
    B() {m_c = 3;}
    ~B() {}
    void fun() {printf("%d", m_c);}
private:
    int m_c;
};
 
void main()
{
    A a;
    B *pb = (B*)(&a);
    pb->fun();
}

    最后输出的是什么。内存中实例化了一个A类对象,然后将该地址强制转换成一个B类地址,即将该对象的地址内容强制看成一个B类对象。pb为B类的指针,理所当然调用的是B类中的fun()函数(可以跟多态的情形相比较),当调用fun()函数时,调用对象与该函数进行绑定,即fun()函数中隐含的形参this指针初始化为调用对象(A类对象)的地址,假设为0xff80。然后fun()函数打印值m_c。这里要注意,对象在访问类成员时,编译器并没有存储该对象各个成员的实际地址,而是存储了其相对于当前对象首地址的偏移量,由于B类只有一个成员m_c,在编译阶段,编译器就记录了m_c对于B类对象的偏移量为0,故访问m_c时,便是访问当前对象地址this+偏移量0,注意,this在这里绑定的是A类对象的首地址,在A类中,偏移量为0的成员是m_a,故打印出m_a的值。

版权声明:本文为zl6481033原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zl6481033/article/details/90436755

智能推荐

《深入探索C++对象模型》第四章:Function语意学

1、Member的各种调用方式 在C++中,支持三种类型的member functions:static、nonstatic和virtual,每一种类型被调用的方式都不一样,下面我们分别来探讨一下: (1)Nonstatic C++要求非静态成员函数必须要和一般的非成员函数具有相同的效率。采取的措施是通过以下步骤将成员函数实例转换为对等的非成员函数实例: a.改写函数的原型(signature)以...

深度探索C++对象模型——Function语意学

前言: 最近在读《深度探索C++对象模型》,收获不小,整理一些笔记,一来总结加体悟,二来希望以后在某些知识点的遗忘时能快速拾起也希望对读者有一定的帮助。 C++支持三种类型的成员函数 static nonstatic virtual static成员函数 静态成员函数不能直接存取非静态成员(包括静态成员数据和静态成员函数):因为静态成员函数是属于类的,不属于任何一个对象,在类对象创建以前就已经存在...

《深度探索C++对象模型》第二章 构造函数语意学

Default Constructor的构建操作 default constructors在需要的时候被编译器产生。 例: 上述的代码情况中,并不会生成一个deafult constructor。 需要注意的地方是: 全局的object内存在被**时会清0,而局部对象配置于堆栈中,不一定会清0,它们的内容是内存上次被使用的遗留值。 接下来分4种情况讨论编译器会产生default construct...

C++对象模型学习——Data语意学

2019独角兽企业重金招聘Python工程师标准>>>      对于下面代码,sizeof的结果:     结果的大小和机器还有编译器都有关。     从class X并不是空,它有一个隐藏的1byte大小,这是编译器安插进去的一个char。这使得这 一class的两个objec...

C++对象模型之Data语意学

数据成员的绑定 在早期的编译器上,,如果一个类中的成员有一个和全局对象同名的数据成员,那么当在成员函数中访问其数据成员时,就有可能被绑定成了全局对象,而非数据成员。在现有的编译器中对成员函数本身的分析直到整个类的声明都出现了才开始,因此最后的数据成员的绑定会达到预期结果。也就是不会出现上述情况。 然而,对于成员函数的参数列表却并不是这样的。如下所示: 很明显我们期望的是参数列表中的Length为f...

猜你喜欢

深度探索C++对象模型 第3章 Data语意学 3.2 data member的布局 3.3 Data member的存取

3.2 data member的布局 一、数据成员的布局规则 每一个Point3d对象是由3个float组成,其在内存中排列的次序是x,y,z; static静态对象不属于某个特定的对象,其存放在数据段中 二、c++标准声明数据成员的布局规则 在同一个access section中(public、private、protected),数据的排列只需符合“较晚出现的数据在类对象的较高地址...

《深度探索C++对象模型》阅读笔记 第二章 构造函数语意学

文章目录 0、通常很多C++程序员存在两种误解: 1.包含有带默认构造函数的对象成员的类 2.继承自带有默认构造函数的基类的类 3.带有虚函数的类 4.带有一个虚基类的类 总结 5、命名返回值优化 6、成员初始化队列(Member Initialization List) 0、通常很多C++程序员存在两种误解: 没有定义默认构造函数的类都会被编译器生成一个默认构造函数。 编译器生成的默认构造函数会...

《深度探索C++对象模型》读书笔记:第6章 执行期语意学

6.1 对象的构造解构 destructor会被放在对象被构造出来以后的每一个离开点(当object还存活),如每一个return。所以尽量将object放在使用它的那个区段附近。可以节省不必要的对象产生操作和摧毁操作。 全局对象 identity在被第一次调用之前构造出来,在main函数结束之前被摧毁。并且都是静态的初始化操作和内存释放操作。 munch策略 1.为每一个需要静态初始化的档案产生...

【深度探索C++对象模型读书笔记】【第6章】执行期语意学

一、对象的构造和析构 1、如果一个区段或函数中有一个以上的离开点,destructor必须被放在每一个离开点之前。 2、一般而言object应尽可能放在使用它的那个程序区附近,这样做可以节省不必要的对象产生和销毁操作。 3、C++程序中所有的global objects都被放置在程序的data segment中。如果global object有constructor和destructor的话,它需...

Maven配置本地仓库 Maven项目使用本地仓库

Maven配置本地仓库 Maven项目使用本地仓库 项目部署或开发环境没有外网的情况下, 需要配置本地仓库. 由于是在内网环境,maven无法连接互联网,所以只能事先将jar下载到本地,然后通过配置pom文件,将jar引用至本地仓库即可。 修改maven的settings.xml文件 配置本地仓库目录 修改maven项目中的pom.xml 添加如下配置 配置环境变量 参考链接: https://b...