深度探索C++对象模型(12)——数据语义学(2)——数据成员布局、数据成员存取

本文深入探讨C++对象模型,主要关注数据成员的布局和存取。首先,介绍成员变量地址规律,说明成员变量按照定义顺序存储,并讨论了字节对齐对内存布局的影响。接着,通过代码示例展示如何打印成员变量的偏移值。然后,阐述静态成员变量的存取方式,包括类名直接访问、对象和对象指针访问,并指出静态成员变量的唯一性和地址不变性。最后,简要提及非静态成员变量的存取方式及其编译器处理机制。
摘要由CSDN通过智能技术生成

1.数据成员布局

 (1)成员变量地址规律

代码:

#include <iostream>
#include <stdio.h>
using namespace std;

//定义一个类

class MYACLS
{
public:
	int m_i;
	static int m_si; //声明不是定义

	int m_j;
	static int m_sj;

	int m_k;
	static int m_sk;
};



int MYACLS::m_sj = 0; //这才是定义;

int main()
{
	MYACLS myobj;
	cout << sizeof(myobj) << endl;

	myobj.m_i = 2;
	myobj.m_k = 8;
	myobj.m_j = 5;

	printf("myobj.m_i = %p\n", &myobj.m_i);
	printf("myobj.m_j = %p\n", &myobj.m_j);
	printf("myobj.m_k = %p\n", &myobj.m_k);

	MYACLS *pmyobj = new MYACLS();
	printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
	printf("pmyobj->m_j = %p\n", &pmyobj->m_j);
	printf("pmyobj->m_k = %p\n", &pmyobj->m_k);

}

运行结果:

 普通成员变量的存储顺序 是按照在类中的定义顺序从上到下来的;
 比较晚出现的成员变量在内存中有更高的地址;
 类定义中pubic,private,protected的数量,不影响类对象的sizeof;

(2)字节对齐

代码:

#include <iostream>
#include <stdio.h>
using namespace std;

//定义一个类
//#pragma pack(1) //对齐方式设置为1字节对齐(不对齐)
class MYACLS
{
public:
	int m_i;
	static int m_si; //声明不是定义

	int m_j;
	static int m_sj;

	int m_k;
	static int m_sk;

	char m_c;  //1字节
	int m_n;   //4字节
};
//#pragma pack() //取消指定对齐,恢复缺省对齐;


int MYACLS::m_sj = 0; //这才是定义;

int main()
{
	MYACLS myobj;
	cout << sizeof(myobj) << endl;

	myobj.m_i = 2;
	myobj.m_k = 8;
	myobj.m_j = 5;

	printf("myobj.m_i = %p\n", &myobj.m_i);
	printf("myobj.m_j = %p\n", &myobj.m_j);
	printf("myobj.m_k = %p\n", &myobj.m_k);
	printf("myobj.m_c = %p\n", &myobj.m_c);
	printf("myobj.m_n = %p\n", &myobj.m_n);

	MYACLS *pmyobj = new MYACLS();
	printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
	printf("pmyobj->m_j = %p\n", &pmyobj->m_j);
	printf("pmyobj->m_k = %p\n", &pmyobj->m_k);
	printf("pmyobj->m_c = %p\n", &pmyobj->m_c);
	printf("pmyobj->m_n = %p\n", &pmyobj->m_n);
}

运行结果:

某些因素会导致成员变量之间排列不连续,就是边界调整(字节对齐),调整的目的是提高效率,编译器自动调整;
调整:往成员之间填补一些字节,使用类对象的sizoef字节数凑成 一个4的整数倍,8的整数倍;

但为了统一个平台下编译器的字节对齐问题,如网络传输中字节不统一的问题,引入了新概念——一字节对齐,即不采用字节对齐方式,就是在类的定义前后加上下述代码

#pragma pack(1) //对齐方式设置为1字节对齐(不对齐)
……
#pragma pack() //取消指定对齐,恢复缺省对齐;

再次运行结果:

(3)打印成员变量的偏移值

成员变量偏移值,就是这个成员变量的地址,离对象首地址偏移多少;

代码:

//三种方法
#include <iostream>
#include <stdio.h>
using namespace std;

#define GET(A,m) (int)(&((A*)0)->m)     //方法2

//定义一个类
#pragma pack(1) //对齐方式设置为1字节对齐(不对齐)
class MYACLS
{
public:
	int m_i;
	static int m_si; //声明不是定义

	int m_j;
	static int m_sj;

	int m_k;
	static int m_sk;	

	char m_c;  //1字节
	int m_n;   //4字节

private:
	int m_pria;
	int m_prib;

public:
	void printMemPoint()
	{
		cout << "打印成员变量偏移值------方法1------" << endl;
		printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
		printf("MYACLS::m_j = %d\n", &MYACLS::m_j);
		printf("MYACLS::m_k = %d\n", &MYACLS::m_k);
		printf("MYACLS::m_c = %d\n", &MYACLS::m_c);
		printf("MYACLS::m_n = %d\n", &MYACLS::m_n);

		printf("MYACLS::m_pria = %d\n", &MYACLS::m_pria);
		printf("MYACLS::m_prib = %d\n", &MYACLS::m_prib);

		cout << "打印成员变量偏移值------方法2------" << endl;
		cout << GET(MYACLS, m_prib) << endl;    

	}
public:
	virtual void myfv() {}
};
#pragma pack() //取消指定对齐,恢复缺省对齐;


int MYACLS::m_sj = 0; //这才是定义;

int main()
{	
	
	MYACLS myobj;
	cout << sizeof(myobj) << endl;

	myobj.m_i = 2;
	myobj.m_k = 8;
	myobj.m_j = 5;

	printf("myobj.m_i = %p\n", &myobj.m_i);
	printf("myobj.m_j = %p\n", &myobj.m_j);
	printf("myobj.m_k = %p\n", &myobj.m_k);
	printf("myobj.m_c = %p\n", &myobj.m_c);
	printf("myobj.m_n = %p\n", &myobj.m_n);

	MYACLS *pmyobj = new MYACLS();
	printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
	printf("pmyobj->m_j = %p\n", &pmyobj->m_j);
	printf("pmyobj->m_k = %p\n", &pmyobj->m_k);
	printf("pmyobj->m_c = %p\n", &pmyobj->m_c);
	printf("pmyobj->m_n = %p\n", &pmyobj->m_n);

	pmyobj->printMemPoint();

    cout << "打印成员变量偏移值------方法3------" << endl;
	//成员变量指针
	int MYACLS::*mypoint = &MYACLS::m_n;
	printf("pmyobj->m_n偏移值 = %d\n", mypoint);
	

	return 1;
}

运行结果 :

注:对象开始的数据成员为虚函数表指针

2.数据成员存取

(1)静态成员变量存取

静态成员变量,可以当做一个全局量,但是他只在类的空间内可见;引用时有三种方式

类名::静态成员变量

对象.静态成员变量

对象指针->静态成员变量

代码:

#include <iostream>
#include <stdio.h>
using namespace std;

class MYACLS
{
public:
	int m_i;
	static int m_si; //声明而不是定义
	int m_j;
};

int MYACLS::m_si = 10; //这个是定义


int main()
{


	MYACLS myobj;
	MYACLS *pmyobj = new MYACLS();

	//对静态成员变量的3种调用方式
	cout << MYACLS::m_si << endl;
	cout << myobj.m_si << endl;
	cout << pmyobj->m_si << endl;
	//用三种调用方式给变量赋值
    //三种方法编译成汇编语句没有任何区别
	MYACLS::m_si = 1;
	cout << MYACLS::m_si << endl;
	myobj.m_si = 2;
	cout << MYACLS::m_si << endl;
	pmyobj->m_si = 3;
	cout << MYACLS::m_si << endl;
	//打印类中成员变量的地址
	printf("myobj.m_i = %p\n", &myobj.m_i);
	printf("pmyobj->m_i = %p\n", &pmyobj->m_i);
	printf("MYACLS::m_si = %p\n", &MYACLS::m_si);
	printf("myobj.m_si = %p\n", &myobj.m_si);
	printf("pmyobj->m_si = %p\n", &pmyobj->m_si);

	return 1;
}

运行结果:

第一次:

第二次:

结论:静态成员变量只有一个实体,保存在可执行文件的数据段的,在编译时就会生成好这个可执行文件,所以每次执行地址都不会改变

(2)非静态成员变量存取

非静态成员变量的存取(普通的成员变量),存放在类的对象中,存取通过类对象(或类对象指针)

代码:

#include <iostream>
#include <stdio.h>
using namespace std;

class FAC
{
public:
	int m_fai;
	int m_faj;
};
class MYACLS : public FAC
{
public:
	int m_i;
	static int m_si; //声明而不是定义
	int m_j;

	void myfunc()
	{
		m_i = 5;
		m_j = 6;
	}
};

int MYACLS::m_si = 10; //这个是定义


int main()
{
	MYACLS myobj;
	MYACLS *pmyobj = new MYACLS();
	printf("MYACLS::m_i = %d\n", &MYACLS::m_i);
	printf("MYACLS::m_j = %d\n", &MYACLS::m_j);

	pmyobj->myfunc();
	//编译器角度:MYACLS::myfunc(pmyobj)
	//MYACLS::myfunc(MYACLS *const this)
	//{
	//this->m_i = 5;
	//this->m_j = 5;
	//}

	pmyobj->m_faj = 7;

	myobj.m_i = 15;
	pmyobj->m_i=13;


	return 1;
}

反汇编:

对于普通成员的访问,编译器是把类对象的首地址加上成员变量的偏移值,比如  &myobj + 4  = &myobj.m_j;

可以发现对于普通类的成员,不管是  对象.成员变量  还是  对象指针->成员变量  访问效率差不多,但对于虚基类这两种访问方式有区别,见后面的博客;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值