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

本文详细探讨了C++中成员变量的存储顺序、静态成员变量与普通成员变量在内存中的位置、字节对齐的原理和调整方法,以及如何通过不同的方式获取成员变量的偏移值。
摘要由CSDN通过智能技术生成

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

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

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
}

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值