C++内存管理

目录

一、内存的划分 

1.各个内存区域

2.小练习

二、C++的动态内存管理

1.new和delete的引入

2.new和delete的使用

3.内存泄漏

三、深入了解new和delete关键字

1.operator new与operator delete函数

2.定位new(了解)


一、内存的划分 

1.各个内存区域

程序的运行需要内存,对于我们以前常用的32位操作系统,指针变量的大小为四字节,地址个数一共有2的32次方个,每一个字节加起来就是4G;而对于我们现在常用的64位操作系统,指针变量的大小为八字节,地址个数一共有2的64次方个,每一个字节加起来理论上是17179869184G,也就是16777216T,但是我们现在使用的电脑普遍是8G或者16G内存,所以内存空间对于地址的利用并不完全。

我们为了有效地使用内存,避免程序读写遇到空间上的冲突。就需要将内存划分为不同的区域,主要包括:内核空间、栈、堆、内存映射段、数据段、代码段。

  • 内核空间:类似于之前Linux讲的内核kernel,这是操作系统工作的空间我们一般人不能操作
  • 栈:栈又叫堆栈,栈区中主要存放函数的返回值/函数的参数/非静态的成员变量,栈的特点是向下增长的(从栈申请的内存地址会越来越小)。
  • 内存映射段:是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存,做进程间通信。
  • 堆区:主要要用于程序的动态开辟,堆的特点是向上增长的。
  • 数据段:存放全局变量和静态变量。
  • 代码段:存储可执行文件的指令(定义过的函数等)和只读常量。

C++内存管理_第1张图片

注意这里格子的大小不代表每个区域空间的大小。

2.小练习

根据下图中的代码选择数据的储存位置

(1)选择题:

  选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

  globalVar在哪里?____  staticGlobalVar在哪里?____

  staticVar在哪里?____  localVar在哪里?____

  num1 在哪里?____

  char2在哪里?____   *char2在哪里?___

  pChar3在哪里?____   *pChar3在哪里?____

  ptr1在哪里?____    *ptr1在哪里?____

(2)填空题:

  sizeof(num1) = ____;  

  sizeof(char2) = ____;   strlen(char2) = ____;

  sizeof(pChar3) = ____;   strlen(pChar3) = ____;

  sizeof(ptr1) = ____;

C++内存管理_第2张图片

选择题答案:

  选项: A.栈 B.堆 C.数据段(静态区) D.代码段(常量区)

  globalVar在哪里?C(全局变量)  staticGlobalVar在哪里?C(静态变量)

  staticVar在哪里?C(静态变量)  localVar在哪里?A(局部变量)

  num1 在哪里?A(数组名是首元素地址,也在栈区)

  char2在哪里?A(数组名是首元素地址,也在栈区)  *char2在哪里?A(数组内容储存在栈区)

  pChar3在哪里?A(地址储存在栈区)   *pChar3在哪里?D(abcd是一个常量字符串,pChar3指向代码段内的这个常量字符串)

  ptr1在哪里?A (地址储存在栈区)   *ptr1在哪里?B(内容在堆区)

填空题答案:

  sizeof(num1) = 40;  (整个数组大小)

  sizeof(char2) = 5(还有一个\0);   strlen(char2) = 4;(不包含\0)

  sizeof(pChar3) = 4/8;(指针都是4/8字节)  strlen(pChar3) = 4;(不包含\0)

  sizeof(ptr1) = 4/8;(指针都是4/8字节)二、C++的内存管理方式

二、C++的动态内存管理

1.new和delete的引入

在C语言中,我们主要用malloc、calloc、realloc三个函数开辟堆区空间,用free释放空间。

在C++中又引入了new和delete进行内存开辟与释放。

#include
using namespace std;
int main()
{
    int* p1 = (int*)malloc(sizeof(int));
    free(p1);

    int* p2 = new int;
    delete p2;

    return 0;
}

上下两行代码起到的作用是一样的,上下都是开辟int类型大小的空间用p1和p2指针维护,然后释放空间。

2.new和delete的使用

new和delete的使用对于内置类型其实没有太多的优化,主要对于类类型申请空间更加方便。

#include
using namespace std;
class Date
{
public:
    Date(int year = 2000, int month = 1, int day = 1)
        :_year(year)
        , _month(month)
        , _day(day)
    {}
    void Dateprint()
    {
        cout << _year << endl;
        cout << _month << endl;
        cout << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    int* p1 = new int;//堆区申请一个int类型的空间
    delete p1;//释放空间

    int* p2 = new int(1);//堆区申请一个int类型的空间并初始化为1
    delete p2;//释放空间

    int* p3 = new int[3];//开辟一个整型数组,可以储存三个元素
    delete[] p3;//释放空间,注意释放数组要使用delete[]

    Date* arr = new Date[10];//开辟一个储存十个Date元素的数组,此处相当于调用了十次构造函数
    delete[] arr;//释放空间,使用delete[],[]内可以写调用几次析构函数,但是不写编译器也可以自己识别
    //但是注意:变量->delete,数组->delete[],一定要前后对应,否则将产生内存泄漏

    return 0;
}

3.内存泄漏

内存泄漏是C/C++程序中最常见的错误,可以说防不胜防。

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制(也就是说我们没有指针维护块空间了),因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

内存泄漏分类:

  • 堆内存泄漏(Heap leak) :堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一 块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分 内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak。
  • 系统资源泄漏 :指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

内存泄漏除了我们在写代码的过程中规避,还有两种方式检查

其实是有的在vs下,可以使用windows操作系统提供的_CrtDumpMemoryLeaks() 函数进行简单检测,当我们调试到该位置,在输出窗口:该函数只报出了大概泄漏了多少个字节,没有其他更准确的位置信息。

三、深入了解new和delete关键字

1.operator new与operator delete函数

这两个全局函数实现了new和delete两个关键字,其实这里的operator并不代表运算符重载,因为在C语言中也不存在这两个关键字,这两个函数很特殊,记住它们不是运算符重载。

void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
    // try to allocate size byte
    svoid *p;
    while ((p = malloc(size)) == 0)
    if (_callnewh(size) == 0)
    {
        // report no memory
        static const std::bad_alloc nomem;
        _RAISE(nomem);
    }
    return (p);
}


void operator delete(void *pUserData)
{
    _CrtMemBlockHeader * pHead;
    RTCCALLBACK(_RTC_Free_hook, (pUserData, 0));
    if (pUserData == NULL)
        return;
    _mlock(_HEAP_LOCK); /* block other threads */
    __TRY
        /* get a pointer to memory block header */
        pHead = pHdr(pUserData);
        /* verify block type */
        _ASSERTE(_BLOCK_TYPE_IS_VALID(pHead->nBlockUse));
        _free_dbg(pUserData, pHead->nBlockUse);
    __FINALLY
        _munlock(_HEAP_LOCK); /* release other threads */
    __END_TRY_FINALLY
    return;
}

operator new 实际也是通过malloc来申请空间,如果 malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施就继续申请,否则就抛异常。

operator delete 最终是通过free来释放空间的。

总之,new和delete是C语言中malloc和free的封装,即使我们知道它们的底层实现原理但是我们也不要使用这两个函数。

new/delete和malloc/free的对比:

  • new/detele是关键字,而malloc/free是函数
  • malloc等申请动态内存的函数和new开辟空间成功时都返回开辟空间的首地址,而开辟失败时内存函数返回空指针,new会直接抛异常。
  • new对于自定义类型还会对每一个变量和数组元素调用构造函数,也可以初始化,内存函数只有calloc可以在开辟后初始化为0
  • malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可
  • malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型
  • delete在底层也是通过free释放空间,但是在释放空间前delete会先调用析构函数清理数据

2.定位new(了解)

定位new表达式是在已分配的原始内存空间中调用构造函数初始化一个对象。

使用格式: new (place_address) type或者new (place_address) type(initializer-list) place_address必须是一个指针,initializer-list是类型的初始化列表

使用场景: 定位new表达式在实际中一般是配合内存池使用。因为内存池分配出的内存没有初始化,所以如果是自定义类型的对象,需要使用new的定义表达式进行显示调构造函数进行初始化。

class A
{
public:
    A(int a = 0)
        : _a(a)
    {
        cout << "A():" << this << endl;
    }
    ~A()
    {
        cout << "~A():" << this << endl;
    }
private:
    int _a;
};
// 定位new/replacement new
int main()
{
 
    A* p1 = (A*)malloc(sizeof(A));
    //p1现在指向的只是与A对象相同大小的空间,还不能算是一个对象
    new(p1)A;
    //对这一块空间调用A的构造函数
    p1->~A();
    //p1是A*指针,指向一个已经初始化好的变量,调用内部析构函数
    free(p1);
    //释放空间

    return 0;
}

C++内存管理结束

你可能感兴趣的:(C++,c++,c语言,visual,studio,程序人生)