以下仅为个人思路,有错还望大家及时指出。
1.C++内存空间
参考链接:C++函数调用内存分配机制_zhongguoren666的博客-CSDN博客
线程的堆和栈_JackLiu16的博客-CSDN博客_线程是在堆上还是栈上
C/C++变量在内存中的分布_MoreWindows的博客-CSDN博客
C/C++程序内存的分配_cherrydreamsover的博客-CSDN博客_c++内存分配
C/C++动态内存管理malloc/new、free/delete的异同_cherrydreamsover的博客-CSDN博客
C/C++动态内存管理malloc/new、free/delete的异同_cherrydreamsover的博客-CSDN博客
C++主要可分为静态区和动态区。其中C/C++程序内存的分配这篇博客是精髓。
1)栈区(stack):由编译器自动分配与释放,存放为运行时函数分配的局部变量、函数参数、返回数据、返回地址等。其操作类似于数据结构中的栈。
2)堆区(heap):一般由程序员自动分配,需要手动释放,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
3)全局区(静态区static):存放全局变量、静态数据、常量。程序结束后由系统释放。全局区分为已初始化全局区(data)和未初始化全局区(bss)。
4)常量区(文字常量区):存放常量字符串,程序结束后有系统释放。
5)代码区:存放函数体(类成员函数和全局区)的二进制代码。
补充:
1)每个线程一个栈,每个进程一个堆。线程允许共享的是堆中的数据。
参考:
一个进程中各线程的堆和栈的关系_大爱李志的博客-CSDN博客_一个进程几个堆几个栈
2)char* 与char[]
char* 与char[]的内存分配方式比较古灵精怪。char* 分配在常量区,char[]分配在栈区。
参考:
C语言 char*和char[]用法_imxlw00的博客-CSDN博客_c char*
举个例子便可马上理解:
char* ch1() {
char s[] = "123456";//分配在栈区
s[1] = '9';
return s;
}
char* ch2() {
char* s = "123456";//分配在常量区
//s[1]='2'//因为是分配在常量区,因此这个会报错
return s;
}
int main()//int argc,char** argv
{
char* s=ch1();
// cout << sizeof(s) << endl;
for (int i = 0; i != 6; ++i)cout << s[i] << endl;
//无法正常打印,因为ch1()中返回的指针指向一个栈中的地址,而栈在离开作用域后内存会被释放
cout << "*********************" << endl;
s = ch2();
for (int i = 0; i != 6; ++i)cout << s[i] << endl;
//可正常打印,因为ch2()中返回的指针指向一个常量区中的地址,其离开作用域后不会被释放
return 0;
}
有可能char* s = "123456"语句会报错,具体原因可参考:
2.类的数据成员与函数
首先要了解this指针,可参考:
【P115 33】C++ 对象特性—this指针和空指针_R-G-B的博客-CSDN博客_c++ this指针为空
c++中this指针的用法详解_程小二的博客-CSDN博客_c++this指针的用法
C++中this指针的理解和用法_小海歌哥的博客-CSDN博客_this指针的作用和用法
还有虚函数与虚表指针,可参考:
C++虚函数的调用过程_Currybeefer的博客-CSDN博客_c++虚函数调用
以及thiscall的函数调用方式,可参考:
_thiscall调用约定的简单概念_衣兜的博客-CSDN博客_thiscall调用约定
还有最后这个强力推荐的文章,用C语言去详细描述了C++的封装继承和多态的特性:
看之前了解一下结构体指针的强转:计算结构体的大小 - 小乖不乖 - 博客园
struct s1 {
int a = 1;
int b = 0;
};
struct s2 {
long long c = 0;
};
int main()//int argc,char** argv
{
s1 ss;;
s2* sss = (s2*)&ss;
cout << sss->c << endl;//会打印1
}
1)类的所有对象共享同一个成员函数的地址空间,而每个对象有独立的成员变量地址空间,可以说成员函数是类拥有的,成员变量是对象拥有的;不过如果类里有虚函数会多出一个虚函数表指针。
class vir {
public:
int i;//占据一个数据成员
int size = sizeof(*this);//占据一个数据成员
void q() { cout << "q()" << endl; };//普通函数不占据
virtual void q1() { cout << "vir" << endl; };//有虚函数,占据一个虚函数表指针
virtual void q2() {};
vir() {//构造函数不占据
}
};
int main()//int argc,char** argv
{
virson vs;
cout << vs.size << endl;//最终打印为12字节
return 0;
}
2)类的成员函数的默认形参中其实还隐含了一个this指针。
class B {
public:
void p() { cout << "p()" << endl; };//其实是void p(B* this)
virtual void vp() { cout << "vp()" << endl; };//其实是void vp(B* this)
};
int main()
{
((B*) nullptr)->p();//可正常打印,因为该函数中没有使用到成员变量,即使this为空指针也能允许
((B*) nullptr)->vp();//报错,因为使用虚函数先要从this指向的对象空间中索引出虚表指针
}
3)在构造函数中调用虚函数
最好不要这样干,但并非不行。
class vir {
public:
int i;//占据一个数据成员
int size = sizeof(*this);
void q() { cout << "vir" << endl; };
virtual void q1() { cout << "vir" << endl<<size<<endl; };
virtual void q2() {};
vir() {
q1();//打印vir
}
};
class virson:public vir {
public:
virtual void q1() { cout << "virson" << endl; };
//virtual void q2() {};
virson() {
q1();//打印vson
}
};
int main()
{
vir* v = new virson();//vir类型的指针所允许的调用权限仅为vir中拥有的,具有截断性
}
上面可以说明在执行构造函数作用域之前就已经为this开辟好空间并进行初始化了,并且this指针指向的是new出来的类型的空间。
这说明那个时候this指向的空间中的虚函数表指针已经指向virson对应的虚函数表了。
进一步而参考:c++ 构造函数中调用虚函数的实现方法
3.虚析构函数与内存泄漏
析构函数没用好,很可能会造成内存泄漏。通过上面的分析能比较好地清楚为啥有的析构函数是要用虚函数了。可参考:
虚函数、虚析构函数的缺点 - CLive Studio - 博客园
C++中虚析构函数的作用及其原理分析_逐鹿之城的博客-CSDN博客_虚析构函数的作用
对于虚析构函数的理解_xld_hung的博客-CSDN博客_虚析构函数
大致总结一下:如果析构函数不加virtual,那么会按照普通成员函数的调用方式,即指针类型去索引到该函数,这样的话就会调用到基类的析构函数。但是当加上virtual之后,其是要通过指针对应的数据类型去里面调用虚函数表指针索引到相应的函数,这时候才会调用派生类所对应的析构函数了。而执行了派生类的析构函数会自动再去调用基类的析构函数,因此在基类中采用虚析构函数,在delete一个指向派生类的基类指针时会执行派生类跟基类的析构函数。
下面举个例子说明会内存泄漏的情况:
class vir {
public:
int size = sizeof(*this);
int i;
void q() { cout << "q()" << endl; };
virtual void q1() { cout << "vir" << endl<<size<<endl; };//有虚函数,占据一个虚函数表指针
vir() {//构造函数不占据
q1();
}
virtual ~vir() {
cout << "virdelete" << endl;
q1();
cout << i << endl;//能正常打印,i=90,说明执行virson的时候并没有把vir中的部分删除
}
};
class virson:public vir {
public:
virtual void q1() {
cout << "virson" << endl;
};
int* s = new int(3);
virson() {
// q1();
}
virtual ~virson() {
q1();
delete s;//如果不调用virson的析构函数,s指向的空间将变成野空间
i = 90;
}
};
int main()
{
vir* vs = new virson;
delete vs;
}