C++-----深度探索对象模型-第三章-Data语意学(二)

本文详细介绍了C++对象模型中的数据语意学,包括非静态成员的排列顺序、访问区段的组织、虚拟机制下vptr的位置、静态成员的存储与访问、以及多态情况下通过对象指针存取成员的差异。同时,文章讨论了编译器如何通过对象的地址和成员偏移量来访问成员,以及在特定情况下的笔试题解析。
摘要由CSDN通过智能技术生成

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的值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值