c++学习之内存管理

简介: c++学习之内存管理

1.c/c++内存分布

在学习c语言的时候我们就已经了解到不同的数据存储在不同的空间当中,对于内存,其被划分成不同的各个区域,分别用来管理不同的数据,如下图:


b0ad35ea129e429bba2d4499959eeeb8.png可以看到内存被划分为堆区,栈区,静态区,代码段区,内存映射段,数据段。

每一种区域都存放着不同的数据。

1. 又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。

我们常定义的函数,普通变量等都存放在栈区。

2. 内存映射段 是高效的 I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口

创建共享共享内存,做进程间通信。(Linux课程如果没学到这块,现在只需要了解一下)

3. 用于程序运行时动态内存分配,堆是可以向上增长的。

对于数据结构中,我们常常开辟堆区的内存保存数据,在c++中也是如此且有新方法。

4. 数据段--存储全局数据和静态数据。static修饰的全局变量与定义的全局变量存放处。

5. 代码段--可执行的代码/只读常量。

其次, c++在继承c语言的基础上,对于堆区上的申请和释放做了优化,这将体现在c++类和自定义数据类型中。

2.new与delete/malloc与free

首先我们知道如何利用c语言的方式向堆区上开辟空间,我们利用malloc,cealloc,realloc这三个函数可以实现向堆区上开辟一块空间,之后我们在进行初始化,在对空间的利用完之后,利用free函数释放掉对上的空间,实现动态内存管理。

如下代码:

int main()
{
  char* p1 = (char*)malloc(sizeof(char)*10); 
  free(p1);
  char* p2 = (char*)calloc(char ("hello world"), sizeof(char)*10);
  char *p3 = (char*)realloc(p2, sizeof(char) * 10);
  free(p3);
  return 0;
}

对于c语言的动态内存管理:

1.首先大佬认为如此的设计使得动态内存管理过于冗余,对于空间的开辟较为麻烦。

2.我们需要强制类型转换并计算出所需该类型的大小。

c++内存管理方式:

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因

此C++又提出了自己的内存管理方式通过new和delete操作符进行动态内存管理

new/delete操作内置类型:


5b0ca56a6d9f430aafac0db989933d84.png

我们能看到对于new的一些操作及运算符重载。

对于c++:我们可以利用new来进行动态内存的申请,利用delete进行内存的释放。

void Test()
{
  // 动态申请一个int类型的空间
  int* ptr4 = new int;
  // 动态申请一个int类型的空间并初始化为10
  int* ptr5 = new int(10);
  // 动态申请10个int类型的空间
  int* ptr6 = new int[3];
  //动态申请5个int类型的空间并初始化
  int *ptr7=new int[5]{1,2,3,4,5};
  delete ptr4;
  delete ptr5;
  delete[] ptr6;
  delete[] ptr7;
}

可以看到我们可以初始化空间,并且对于多个内存的开辟也更加方便,对于大小可以自己推导,如一个整型四个字节,一个字符型一个字节。

注意:这里在开辟多个或一个空间时,delete的写法也应与之对应,否则存在释放报错。

因为c++兼容c语言,所以除了书写简便,对于内置类型,功能是一样的。

new/delete操作自定义类型

如果仅仅是因为优化写法,那么在c++设计new和delete是纯属没有必要的,真正牛逼得地方是new与delete可以操作自定义类型!!!而这与c++11类和对象密切联系着。

利用malloc与free我们只能做到对空间的开辟与释放,但无法去初始化这个自定义类型的空间。

如自定义类型A:

class A
{
public:
  A(int a = 0)
    : a(a)
  {
    cout << "A():" << this << endl;
  }
  ~A()
  {
    cout << "~A():" << this << endl;
  }
   void init_a(int x)
    {
      _a = x;
    }
private:
  int a;
};

对于自定义类型下的malloc:

void test1()
{
  A* p1 = (A*)malloc(sizeof(A));
  //无法调用构造函数来初始化,因为当前无法显式调用构造函数
  p1->_a = 5;//也无法对私有数据操作,只能通过定义接口函数来初始化
  p1->init_a(5);//这也是对于简单的数据,若是设计的类中数据量偌大,则直接就g了
  free(p1);
}

我们可以看到自定义类型的数据要实现初始化太困难且复杂,甚至没法用。

那么对于重新设计的new与delete:

int main()
{
  // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
  //还会调用构造函数和析构函数
  A* p1 = new A;
  A* p2 = new A(5);
  A* p3 = new A[2]{1,2};
  delete p1;
  delete p2;
  delete[]p3;
  return 0;
}

对于初始化new可以再申请空间后调用构造函数,对于释放delete会调用析构函数,之后在释放空间。


686dd2e834ef4888af4c0fab2d993892.png

operator newoperator delete函数

new 和 delete 是用户进行 动态内存申请和释放的操作符 , operator new 和 operator delete 是

系统提供的 全局函数 , new 在底层调用 operator new 全局函数来申请空间, delete 在底层通过

operator delete 全局函数来释放空间。

注意这两个作为函数(对于new和delete的实现),并非是new与delete的重载。

如下段代码:

// try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
比特就业课
通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的。
5. new和delete的实现原理
5.1 内置类型
     if (_callnewh(size) == 0)
     {
         // report no memory
         // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
         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;
}
/*

对于这里的new内存开辟失败的时候,这里设计利用异常进行判断。

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果

malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施

就继续申请,否则就抛异常,程序遇到异常就直接终止了。operator delete 最终是通过free来释放空间的。

newdelete的实现原理

 内置类型

 if (_callnewh(size) == 0)
    {
        // report no memory
        // 如果申请内存失败了,这里会抛出bad_alloc 类型异常
        static const std::bad_alloc nomem;
        _RAISE(nomem);
    }
return (p);
}
/*
operator delete: 该函数最终是通过free来释放空间的
*/
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;
}
/*
free的实现
*/
#define   free(p)               _free_dbg(p, _NORMAL_BLOCK)

如果申请的是内置类型的空间, new 和 malloc , delete 和 free 基本类似,不同的地方是:

new/delete 申请和释放的是单个元素的空间, new[] 和 delete[] 申请的是连续空间,而且 new 在申

请空间失败时会抛异常, malloc 会返回 NULL 。

自定义类型

new的原理

1. 调用operator new函数申请空间

2. 在申请的空间上执行构造函数,完成对象的构造

delete 的原理

1. 在空间上执行析构函数,完成对象中资源的清理工作

2. 调用 operator delete 函数释放对象的空间

new T[N]的原理

1. 调用operator new[]函数,在operator new[]中实际调用operator new函数完成N个对

象空间的申请

2. 在申请的空间上执行N次构造函数

delete[]的原理

1. 在释放的对象空间上执行 N 次析构函数,完成 N 个对象中资源的清理

2. 调用 operator delete[] 释放空间,实际在 operator delete[] 中调用 operator delete 来释

放空间

malloc/freenew/delete的区别

malloc/freenew/delete的共同点是:都是从堆上申请空间,并且需要用户手动释放。不同的地

方是:

1. malloc 和 free 是函数, new 和 delete 是操作符

2. malloc 申请的空间不会初始化, new 可以初始化

3. malloc 申请空间时,需要手动计算空间大小并传递, new 只需在其后跟上空间的类型即可,

如果是多个对象, [] 中指定对象个数即可

4. malloc 的返回值为 void*, 在使用时必须强转, new 不需要,因为 new 后跟的是空间的类型

5. malloc 申请空间失败时,返回的是 NULL ,因此使用时必须判空, new 不需要,但是 new 需

要捕获异常

6. 申请自定义类型对象时, malloc/free 只会开辟空间,不会调用构造函数与析构函数,而 new

在申请空间后会调用构造函数完成对象的初始化, delete 在释放空间前会调用析构函数完成

空间中资源的清理


相关文章
|
16天前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
38 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
5天前
|
编译器 C语言 C++
配置C++的学习环境
【10月更文挑战第18天】如果想要学习C++语言,那就需要配置必要的环境和相关的软件,才可以帮助自己更好的掌握语法知识。 一、本地环境设置 如果您想要设置 C++ 语言环境,您需要确保电脑上有以下两款可用的软件,文本编辑器和 C++ 编译器。 二、文本编辑器 通过编辑器创建的文件通常称为源文件,源文件包含程序源代码。 C++ 程序的源文件通常使用扩展名 .cpp、.cp 或 .c。 在开始编程之前,请确保您有一个文本编辑器,且有足够的经验来编写一个计算机程序,然后把它保存在一个文件中,编译并执行它。 Visual Studio Code:虽然它是一个通用的文本编辑器,但它有很多插
28 6
|
22天前
|
存储 程序员 编译器
简述 C、C++程序编译的内存分配情况
在C和C++程序编译过程中,内存被划分为几个区域进行分配:代码区存储常量和执行指令;全局/静态变量区存放全局变量及静态变量;栈区管理函数参数、局部变量等;堆区则用于动态分配内存,由程序员控制释放,共同支撑着程序运行时的数据存储与处理需求。
75 21
|
10天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
13天前
|
存储 C语言 C++
【C++打怪之路Lv6】-- 内存管理
【C++打怪之路Lv6】-- 内存管理
32 0
【C++打怪之路Lv6】-- 内存管理
|
17天前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
35 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
23天前
|
存储 C语言 C++
【C/C++内存管理】——我与C++的不解之缘(六)
【C/C++内存管理】——我与C++的不解之缘(六)
|
26天前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
53 1
|
26天前
|
编译器 C语言 C++
详解C/C++动态内存函数(malloc、free、calloc、realloc)
详解C/C++动态内存函数(malloc、free、calloc、realloc)
95 1
|
26天前
|
Java 编译器 C++
c++学习,和友元函数
本文讨论了C++中的友元函数、继承规则、运算符重载以及内存管理的重要性,并提到了指针在C++中的强大功能和使用时需要注意的问题。
16 1