一。观察成员变量地址规律
静态成员变量 不占用 类对象 的空间
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
}