C++ 中 new 操作符内幕:new operator、operator new、placement new

1、new / delete 具体步骤

new

  • 第一步:调用 operator new 函数分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象
  • 第二步:编译器运行相应的构造函数以构造对象,并为其传入初值
  • 第三部:对象构造完成后,返回一个指向该对象的指针

delete

  • 第一步:调用对象的析构函数
  • 第二步:编译器调用 operator delete 函数释放内存空间

2、new/deletemalloc/free 的区别是什么?

  • malloc/freeC 语言的标准库函数new/deleteC++运算符。它们都可用于申请动态内存和释放内存
  • malloc/free 不会去自动调用构造和析构函数,对于基本数据类型的对象而言,光用 malloc/free 无法满足动态对象的要求
  • malloc/free 需要指定分配内存的大小,而 new/delete 会自动计算所需内存大小
  • new 返回的是指定对象的指针,而 malloc 返回的是 void*,因此 malloc 的返回值一般都需要进行强制类型转换

operator new 重载:

class Person{
public:
    Person(){
        id = 0;
        score = 0;
        cout << "Person()" << endl;
    }
    Person(int id, int score):id(id),score(score){
        cout << "Person(int, int)" << endl;
    }
    Person(const Person &p){
        cout << "Person(const Person &p)" << endl;
        id = p.id;
        score = p.score;
    }
    // operator new
    void* operator new(size_t s){
        cout << "new 1" << endl;
        void* p = malloc(s);
        return p;
    }
    // placement new
    void* operator new(size_t s, void* p){
        cout << "new 2" << endl;
        return p;
    }
    int id;
    int score;
};
int main()
{
    void* p = malloc(sizeof(Person));
    cout << p << endl;
    Person* pp = new (p) Person(10, 20);
    cout << pp << endl;
    cout << pp->id << endl;
    Person* myP = new Person;
    return 0;
}

输出:

0x55b01ec9aeb0
new 2
Person(int, int)
0x55b01ec9aeb0
10
new 1
Person()

3、C++内存管理

C++ 中,虚拟内存分为代码段、数据段、BSS段、堆区、栈区以及文件映射区六部分

  • 代码段:包括只读存储区文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码
  • 数据段:存储程序中已初始化的全局变量和静态变量
  • BSS段:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量(这个段的数据全都是0
  • 堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存
  • 映射区:存储动态链接库以及调用mmap函数进行的文件映射
  • 栈区:使用栈空间存储函数的返回地址、参数、局部变量、返回值(最靠近CPU的区)

4、内存的分配方式(三种)

  • 静态存储区分配:是在程序编译时就已经分配好的,在整个运行期间都存在,如全局变量、常量、静态变量

  • 栈上分配:函数内的局部变量就是从这分配的,但分配的内存容易有限

  • 堆上分配:也称动态分配,如我们用 new,malloc 分配内存,用 delete,free 来释放的内存。堆内存空间可由用户手动分配和释放,所以其生存周期由用户指定,较为灵活。但频繁的分配、释放大小不同的堆空间会产生内存碎片

    内存分配

5、内存池

内存池是一种内存分配方式。通常我们习惯直接使用new、malloc申请内存,这样做的缺点在于所申请内存块的大小不定,当频繁使用时会造成大量的内存碎片并进而降低性能。内存池则是在真正使用内存之前,预先申请分配一定数量、大小相等(一般情况下)的内存块留作备用。当有新的内存需求时,就从内存池中分出一部分内存块,若内存块不够再继续申请新的内存。这样做的一个显著优点是,使得内存分配效率得到提升

6、内存泄漏

内存泄漏一般是指堆内存的泄漏,也就是程序在运行过程中动态申请的内存空间不再使用后没有及时释放,导致那块内存不能被再次使用

7、C++中的不安全是什么概念?

C++中的不安全包括两种:一是程序得不到正确的结果,二是发生不可预知的错误(占用了不该用的内存空间)。可能会发生如下问题:

  • 最严重的:内存泄漏,程序崩溃;
  • 一般严重的:发生一些逻辑错误,且不便于调试;
  • 较轻的:丢失部分数据,就像强制转换一样

8、内存中的堆与栈有什么区别?

  • 申请方式:栈由系统自动分配和管理,堆由程序员手动分配和管理
  • 效率:栈由系统分配,计算机底层对栈提供了一系列支持:分配专门的寄存器存储栈的地址,压栈和入栈有专门的指令执行,因此,其速度快,不会有内存碎片;堆由程序员分配,堆是由C/C++函数库提供的,机制复杂,需要一系列分配内存、合并内存和释放内存的算法,因此效率较低,可能由于操作不当产生内存碎片
  • 扩展方向:栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展
  • 程序局部变量是使用的栈空间,new/malloc动态申请的内存是堆空间;同时,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间