结论:
1.类的静态变量是在定义类的时候分配内存,存储在全局区。(注意:静态变量分配内存是在定义了类之后初始化静态变量才会分配内存,证明在下方)
2.类的非静态变量是在创建类的实例的时候分配内存,可能存储在栈,堆。
3.类的静态成员函数和非静态成员函数在类创建以后就在代码段分配了内存。但是类的静态成员函数是在类创建以后就能访问的,而类的非静态成员函数只有在类创建实例以后才能访问。
验证第三条结论:
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
void fun1(){};
void fun2(){};
};
int main()
{
//A x;
cout << sizeof(A)<<endl;
printf("%p\n" ,(void*)(&A::fun1));
return 0;
}
这个是类的非静态函数的地址,应该存储在代码段,每个实例里的函数地址其实都指向的是代码段,包括静态函数也是这样的。静态函数和非静态函数的区别在于有没有this指针,编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把当前对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。而不是一些博客里写的静态函数在类创建以后就分配了内存,而非静态函数在实例创建以后才分配了内存。
值的注意的是,所有函数的代码都存在内存里的代码区。
验证第二条结论:
#include <iostream>
using namespace std;
class A
{
public:
int a;
int b;
void fun1(){};
void fun2(){};
};
int main()
{
A x;
cout << sizeof(A)<<endl;
printf("%p\n" ,(void*)(&A::fun1));
//printf("%p\n" , &(A::a));
//printf("%p\n" , &(A::b));//非静态成员变量在类中不占内存。这上面两条命令会报错提示没有实例
//printf("%p\n" , &A::a);
//printf("%p\n" , &A::b);//这两个不是类中变量的地址,这个输出结果为他在内存里的偏移量
printf("%p\n" , &x);
printf("%p\n" , &x.a);
printf("%p\n" , &x.b);//在实例中,非静态变量才会分配内存
return 0;
}
从输出结果可以看出,实例的地址,和实例中第一个非静态变量的地址是一致的。实例的地址可以在栈或堆,在图中的代码中,存储在栈中。类的大小和类内的函数大小没有关系,这就是因为函数定义在代码段。从输出地址也可以看出,函数地址和实例的位置不在一个地方。非静态成员函数具有this指针,指向调用该函数的对象,一般情况下是作为隐形参数,所以只能存在于非静态成员函数中。
在《C++函数编译原理和成员函数的实现》一节中讲到,成员函数最终被编译成与对象无关的普通函数(即都放在代码段) 。除了成员变量,实例会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是this,它是成员函数和成员变量关联的桥梁。这段实际上就是说非静态成员函数在编译阶段,当对象调用非静态成员函数时,实际上是通过this指针传给函数当前对象的地址,函数内部就可以直接访问对象的成员变量。
参考了:https://baijiahao.baidu.com/s?id=1653384309305389312&wfr=spider&for=pc
对于输出实例中非静态变量的地址也可以有以下方式:
#include <iostream>
using namespace std;
class C{
public:
void fn()
{
printf("%p\n" , &C::a); //offset
printf("%p\n" , &C::b);
printf("%p\n" , &(C::a));//location
printf("%p\n" , &(C::b));
}
int a;
int b;
static int f;
};
int C::f=0;
int main(){
C c;
c.fn();
printf("%p\n" , &c.a);
printf("%p\n" , &c.b);
cout<<"xxxxxxxxxxxxxxxxxxxx"<<endl;
C c1;
c1.fn();
printf("%p\n" , &c1.a);
printf("%p\n" , &c1.b);
return 0;
}
补充:对于静态变量的说明(验证第一条)
类中的静态变量和局部静态变量是类似的,他们都存储在全局区,同时只被初始化一次,在全局区只存在一个静态变量,局部静态变量在函数里每一次调用和修改,都修改的是全局区的这个变量值,所以相当于它在多次函数调用之间是共享的。同理,类中的静态变量也是由各个类的实例共享的,类中的静态变量在全局区也仅有一份,但类的静态全局变量仅有一份,这个是需要定义的,才会开辟内存,若只是在class里面声明,并不会开辟内存,验证如下:
using namespace std;
class C{
public:
void fn()
{
printf("%p\n" , &C::a); //offset
printf("%p\n" , &C::b);
printf("%p\n" , &(C::a));//location
printf("%p\n" , &(C::b));
}
int a;
int b;
static int f;//这是声明
};
//int C::f=0;//要在这里定义
int main(){
//int C::f=0; //若在这定义静态变量,则会报错,“qualified-id in declaration before ‘=’ token”
printf("%p\n" , &(C::f));
}
这段代码验证的是当类的静态变量没有定义时,是否开辟内存空间?
#include <iostream>
using namespace std;
class C{
public:
void fn()
{
printf("%p\n" , &C::a); //offset
printf("%p\n" , &C::b);
printf("%p\n" , &(C::a));//location
printf("%p\n" , &(C::b));
}
int a;
int b;
static int f;
};
int C::f=0;
int main(){
printf("%p\n" , &(C::f));
return 0;
}
结论显然,未定义时,静态变量不会开辟内存空间。
还有一点重要的是,类的静态变量的定义位置,为什么必须写在类外,同时,当类写在头文件的时候,类的静态变量的定义不能写进头文件,这就是和上面说的,为了各个实例能共享他,他只能被定义一次,所以写在类内的话,相当于每一个对象都定义了一次,若将他写进了头文件,那么会多次定义而报错,所以定义类的静态变量最合适的方式就是,类写在头文件,类的实现写在一个cpp文件中,在这个cpp文件中定义类的静态变量,这就保证了在整个程序中,类的静态变量只定义了一次。