1c++的内存管理机制
栈 | 局部变量,函数执行结束之后释放内存 | 内存分配通过处理器指令集 | 分配的内有限 | 容易产生内存泄露 | 内存地址减小的方向 |
堆 | delete来释放内存块 | new 来分配的内存块 动态分配 | 容易产生空间不连续和碎片 | 对于32位系统来说,有4g的堆。 | 内存地址增加的方向 |
自由存储区 | free 来释放 | malloc来分配内存块 | |||
全局静态区域 | |||||
常量存储区 | 不允许修改的部分 |
1.1 内存分配的集中方式
- 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量。
- 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
- 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
2 多线程环境下的内存管理
原因heap 的内存分配是可以改动,因此多线程系统充斥着竞速状态:race conditions
所以,对于线程感知的程序员,必须使用同步,否则使用无所的算法和并发访问时候,会导致heap的数据结构败坏。
1、和进程相比,它是一种非常"节俭"的多任务操作方式。在linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。(资源)
2、运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。(切换效率)
3、线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进城下的线程之间贡献数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。(通信)
除以上优点外,多线程程序作为一种多任务、并发的工作方式,还有如下优点:
1、使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。(CPU设计保证)
2、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序才会利于理解和修改。(代码易维护)
原文链接:https://blog.csdn.net/Damage233/article/details/81116115
3 例外
STL容器所使用的heap内存是由容器所拥有的分配器对象管理,而不是new and delete。
4 下面是声明 operater new 的标准程序库
namespace std{
typedef void (*new_handler)(); //定义一个指针指向函数;该函数没有参数和返回
new_handler set_new_handler(new_handler p) throw();//获得一个new_handler 并返回一个new_handler.
// throw() 表示该函数不抛出任何异常。
}
5 处理内存分配失败的情况
class X{
public:
static void outofMemory;
}
class Y{
public:
static void outofMemory;
}
X *p1 = new X; //如果分配不成功, 那么就调用本来的X::outofMemory.
Y *P2 = new Y;
6 C++的class 专属的new_handler
//资源处理类
class NewHandlerHolder{
public:
explicit NewHanderHolder(std::new_hander nh):handler(nh){}
//取得目前的new-handler
~NewHanderHolder()
{std::set_new_handler(handler);}
private:
std:new_handler handler;
NewHanderHolder(const NewHandlerHolder&);//阻止copy
///
NewHanderHolder& operater= (const NewHandlerholder&);
}
7 new、delete、malloc、free关系
new | new调用构造函数 | 运算符 | delete与new配套,delete []与new []配套 |
delete | 会调用对象的析构函数 | 运算符 | delete只会调用一次析构函数 delete[]会调用每一个成员的析构函数 |
malloc | 标准库函数 | ||
free | free只会释放内存 | 标准库函数 |
8 delete的过程
当delete操作符用于数组时,它为每个数组元素调用析构函数,然后调用operator delete来释放内存。
9 C++有哪些性质(面向对象特点):封装,继承和多态。
10 子类析构时要调用父类的析构函数吗?
定义一个对象时:1. 先调用基类的构造函数=》2. 然后调用派生类的构造函数;
析构的时候恰好相反:1. 先调用派生类的析构函数=> 2. 然后调用基类的析构函数。
11 多态,虚函数,纯虚函数
多态:是对于不同对象接收相同消息时产生不同的动作。
多态的体现:运行和编译。
多态的运行的实现:继承和虚函数。
多态的编译的实现:函数和运算符的重载。
虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
纯虚函数的作用:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在。
纯虚函数不具备函数的功能,一般不能直接被调用。
抽象类:从基类继承来的纯虚函数=》在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为 抽象类。
11.1 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写:是指子类重新定义父类虚函数的方法。
重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!
重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)
12 什么是“引用”?申明和使用“引用”要注意哪些问题?
引用:某个目标变量的“别名”(alias)。
引用的特性:1 对应用的操作与对变量直接操作效果完全相同。
2 申明一个引用的时候,切记要对其进行初始化。
3 声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
13 将“引用”作为函数参数有哪些特点?
1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
14 用过哪些设计模式,单例模式,观察者模式的多线程安全问题