80 C++对象模型探索。数据语义学 - 数据成员布局-成员变量的地址规律,字节对齐问题,成员变量偏移值

一。观察成员变量地址规律

静态成员变量 不占用 类对象 的空间

1.普通成员变量的存储顺序,是按照在类中的定义顺序从上到下 来的

class Teacher4 {
public:
	int m_i;
	static int m_si;//这里是声明一个static,并不是定义,声明不会分配空间
	int m_j;
	static int m_sj;
	int m_k;
	static int m_sk;
};

int Teacher4::m_si = 100;//静态成员变量 不占用 类对象 的空间,实际上从上一节我们知道,静态成员变量是放在数据段或者BSS段的,也就是说,在编译阶段就已经确定了地址。


void main() {
	Teacher4 tea;
	cout<< sizeof(tea) << endl;//12

	//普通成员变量的存储顺序,是按照在类中的定义顺序从上到下 来的
	//如下的代码我们故意先给 m_k = 8,然后再m_j = 5;
	tea.m_i = 2;
	tea.m_k = 8;
	tea.m_j = 5;

	//堆上呢?
	Teacher4* ptea =  new Teacher4();
	printf("堆上分配空间 ptea->m_i 的地址 = %p \n", &ptea->m_i);
	printf("堆上分配空间 ptea->m_j 的地址 = %p \n", &ptea->m_j);
	printf("堆上分配空间 ptea->m_k 的地址 = %p \n", &ptea->m_k);
	
	//  堆上分配空间 ptea->m_i 的地址 = 00DF0958
	//	堆上分配空间 ptea->m_j 的地址 = 00DF095C
	//	堆上分配空间 ptea->m_k 的地址 = 00DF0960
	
	//规律1:比较晚出现的成员变量,地址比较高
	//规律2:类定义中 public,private,protected有多少了,都不会影响sizeof(Teacher4)
	

	cout << "duandian " << endl;
}

我们看到,普通成员变量 的 存储顺序,是按照在类中的定义顺序从上到下 来的

注意 vs2017中的查看某个变量的地址方法, 和查看mem的方法

二。边界调整,字节对齐

//字节调整分析

//某些因素会导致成员变量之间排列不连续,这就是自己对齐,调整的目的是提高效率,其过程是编译器自动调整的

//如何调整:往成员之间填补一些字节,使类对象的sizeof,变成一个4的整数倍,有的时候变成8的整数倍

//关于字节对齐实际上再前面已经分析过了,参考:
CSDN

//不加#pragma pack(1) 运行结果是 20, 使用#pragma pack(1) 结果就变成了17

//这种字节对齐有个问题,就是不同的编译器上,

//字节对齐方式是不同的。例如在windows 的 vs2017 和linux上的g++就是不同的。

//在实际开发中,就会有问题。例如网络程序,你怎么知道对方的用的是windows电脑还是linux,还是apple

//因此,为了统一字节,引入一个概念,叫一字节对齐。也就是不对齐

//一字节对齐 使用方式是在class 文件的头部写 #pragma pack(1) 表示1字节对齐。

//然后再class最后边 写上 #pragma pack() 表示结束。

//也就是说,可以只对某一个类进行字节对齐声明

//字节调整分析
//某些因素会导致成员变量之间排列不连续,这就是自己对齐,
调整的目的是提高效率,其过程是编译器自动调整的
//如何调整:往成员之间填补一些字节,使类对象的sizeof,
变成一个4的整数倍,有的时候变成8的整数倍
//关于字节对齐实际上再前面已经分析过了,参考:
https://mp.csdn.net/mp_blog/creation/editor/135105140

#pragma pack(1)
class Teacher5 {
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;
	int m_n;
};
#pragma pack() //取消指定对齐方式。恢复默认的对齐方式。


void main() {
	cout << sizeof(Teacher5) << endl;//20, 使用#pragma pack(1) 结果就变成了17
	//这种字节对齐有个问题,就是不同的编译器上,
	//字节对齐方式是不同的。例如在windows 的 vs2017 和linux上的g++就是不同的。
	//在实际开发中,就会有问题。例如网络程序,你怎么知道对方的用的是windows电脑还是linux,还是apple
	//因此,为了统一字节,引入一个概念,叫一字节对齐。也就是不对齐 
	//一字节对齐 使用方式是在class 文件的头部写 #pragma pack(1) 表示1字节对齐。
	//然后再class最后边 写上  #pragma pack() 表示结束。
	//也就是说,可以只对某一个类进行字节对齐声明
}

三,成员变量偏移值的打印

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

方式一:使用 c风格的%p, 显示 &Teacher6::m_i

printf("Teacher6::m_i = %p\n", &Teacher6::m_i);

方式二,使用宏定义

#define GET(A,m) (int) (&((A*)0)->m)

//解释一下这个宏。

//1,(A*)0 的意思是告诉编译器,将0X00000000这块地址用A *去解释。这里就是 用 Teacher6 * 去解释

//2. ((A*)0)就表示这个指针。

//3.由于 ->的优先级是高于 &,

//4.因此要先 计算 ((A*)0)->m,实际上就是找到指针A中的成员变量m。注意只是找到,并没有访问。如果是((A*)0)->m = 90;程序就挂了

//5.找到了 变量m在A中的值。然后再&,就是取地址,因为A* 是00000000,那么&(A*)->m就可以理解为偏移量

方式三,使用成员变量指针

int Teacher6::*pmj = &Teacher6::m_j;

printf("Teacher6::m_j的偏移值为%d\n",pmj); // 4

//还有一种写法,计算偏移值

#define GET(A,m) (int) (&((A*)0)->m)

//解释一下这个宏。
//1,(A*)0 的意思是告诉编译器,将0X00000000这块地址用A *去解释。这里就是 用 Teacher6 * 去解释
//2. ((A*)0)就表示这个指针。
//3.由于 ->的优先级是高于 &,
//4.因此要先 计算 ((A*)0)->m,实际上就是找到指针A中的成员变量m。注意只是找到,并没有访问。如果是((A*)0)->m = 90;程序就挂了
//5.找到了 变量m在A中的值。然后再&,就是取地址,因为A* 是00000000,那么&(A*)->m就可以理解为偏移量



class Teacher6 {
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;
	int m_n;
};

void main() {
	//打印成员变量 距 类的距离
	Teacher6 tea;
	printf("Teacher6::m_i = %p\n", &Teacher6::m_i);
	printf("Teacher6::m_j = %p\n", &Teacher6::m_j);
	printf("Teacher6::m_k = %p\n", &Teacher6::m_k);
	printf("Teacher6::m_c = %p\n", &Teacher6::m_c);
	printf("Teacher6::m_n = %p\n", &Teacher6::m_n);
	cout << "宏定义计算Teacher6::m_n = " << GET(Teacher6,m_n) << endl;

	//Teacher6::m_i = 00000000
	//	Teacher6::m_j = 00000004
	//	Teacher6::m_k = 00000008
	//	Teacher6::m_c = 0000000C
	//	Teacher6::m_n = 00000010
	//  宏定义计算Teacher6::m_n = 16

	// 这里有个额外的问题,使用cout打印的时候,发现 &Teacher6::m_i的值都是1,为啥呢?
	cout <<  (int Teacher6::*)(&Teacher6::m_i) << endl;
	cout << &Teacher6::m_j << endl; //1
	cout << &Teacher6::m_k << endl;//1
	cout << &Teacher6::m_c << endl;//1
	cout << &Teacher6::m_n << endl;//1

	//也可以使用成员变量指针
	int Teacher6::*pmj = &Teacher6::m_j;
	printf("Teacher6::m_j的偏移值为%d\n",pmj); // 4
}
相关推荐
盒马盒马13 分钟前
Redis:cpp.redis++通用接口
数据库·c++·redis
无夜_1 小时前
Prototype(原型模式)
开发语言·c++
刘好念1 小时前
[图形学]smallpt代码详解(1)
c++·计算机图形学
fpcc2 小时前
并行编程实战——TBB框架的应用之一Supra的基础
c++·并行编程
兵哥工控2 小时前
MFC工控项目实例二十二主界面计数背景颜色改变
c++·mfc
兵哥工控2 小时前
MFC工控项目实例二十手动测试界面模拟量输入实时显示
c++·mfc
jyan_敬言2 小时前
【Linux】Linux命令与操作详解(一)文件管理(文件命令)、用户与用户组管理(创建、删除用户/组)
linux·运维·服务器·c语言·开发语言·汇编·c++
笑非不退2 小时前
C++ 异步编程 并发编程技术
开发语言·c++
T0uken3 小时前
【QT Qucik】C++交互:接收QML信号
c++·qt·交互
爱写代码的刚子3 小时前
C++知识总结
java·开发语言·c++