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 在释放空间前会调用析构函数完成

空间中资源的清理


相关文章
|
1月前
|
存储 人工智能 搜索推荐
一种专为AI代理设计的内存层,能够在交互过程中记忆、学习和进化
Mem0 是专为 AI 代理设计的内存层,支持记忆、学习与进化。提供多种记忆类型,可快速集成,适用于开源与托管场景,助力 AI 代理高效交互与成长。
305 123
一种专为AI代理设计的内存层,能够在交互过程中记忆、学习和进化
|
3月前
|
安全 C语言 C++
比较C++的内存分配与管理方式new/delete与C语言中的malloc/realloc/calloc/free。
在实用性方面,C++的内存管理方式提供了面向对象的特性,它是处理构造和析构、需要类型安全和异常处理的首选方案。而C语言的内存管理函数适用于简单的内存分配,例如分配原始内存块或复杂性较低的数据结构,没有构造和析构的要求。当从C迁移到C++,或在C++中使用C代码时,了解两种内存管理方式的差异非常重要。
148 26
|
8月前
|
存储 程序员 编译器
玩转C++内存管理:从新手到高手的必备指南
C++中的内存管理是编写高效、可靠程序的关键所在。C++不仅继承了C语言的内存管理方式,还增加了面向对象的内存分配机制,使得内存管理既有灵活性,也更加复杂。学习内存管理不仅有助于提升程序效率,还有助于理解计算机的工作原理和资源分配策略。
|
4月前
|
C语言 C++
c与c++的内存管理
再比如还有这样的分组: 这种分组是最正确的给出内存四个分区名字:栈区、堆区、全局区(俗话也叫静态变量区)、代码区(也叫代码段)(代码段又分很多种,比如常量区)当然也会看到别的定义如:两者都正确,记那个都选,我选择的是第一个。再比如还有这样的分组: 这种分组是最正确的答案分别是 C C C A A A A A D A B。
76 1
|
10月前
|
存储 缓存 编译器
【硬核】C++11并发:内存模型和原子类型
本文从C++11并发编程中的关键概念——内存模型与原子类型入手,结合详尽的代码示例,抽丝剥茧地介绍了如何实现无锁化并发的性能优化。
431 68
|
7月前
|
存储 Linux C语言
C++/C的内存管理
本文主要讲解C++/C中的程序区域划分与内存管理方式。首先介绍程序区域,包括栈(存储局部变量等,向下增长)、堆(动态内存分配,向上分配)、数据段(存储静态和全局变量)及代码段(存放可执行代码)。接着探讨C++内存管理,new/delete操作符相比C语言的malloc/free更强大,支持对象构造与析构。还深入解析了new/delete的实现原理、定位new表达式以及二者与malloc/free的区别。最后附上一句鸡汤激励大家行动缓解焦虑。
|
10月前
|
算法 网络安全 区块链
2023/11/10学习记录-C/C++对称分组加密DES
本文介绍了对称分组加密的常见算法(如DES、3DES、AES和国密SM4)及其应用场景,包括文件和视频加密、比特币私钥加密、消息和配置项加密及SSL通信加密。文章还详细展示了如何使用异或实现一个简易的对称加密算法,并通过示例代码演示了DES算法在ECB和CBC模式下的加密和解密过程,以及如何封装DES实现CBC和ECB的PKCS7Padding分块填充。
227 4
2023/11/10学习记录-C/C++对称分组加密DES
|
9月前
|
C++ 开发者
C++学习之继承
通过继承,C++可以实现代码重用、扩展类的功能并支持多态性。理解继承的类型、重写与重载、多重继承及其相关问题,对于掌握C++面向对象编程至关重要。希望本文能为您的C++学习和开发提供实用的指导。
144 16
|
8月前
|
安全 C语言 C++
彻底摘明白 C++ 的动态内存分配原理
大家好,我是V哥。C++的动态内存分配允许程序在运行时请求和释放内存,主要通过`new`/`delete`(用于对象)及`malloc`/`calloc`/`realloc`/`free`(继承自C语言)实现。`new`分配并初始化对象内存,`delete`释放并调用析构函数;而`malloc`等函数仅处理裸内存,不涉及构造与析构。掌握这些可有效管理内存,避免泄漏和悬空指针问题。智能指针如`std::unique_ptr`和`std::shared_ptr`能自动管理内存,确保异常安全。关注威哥爱编程,了解更多全栈开发技巧。 先赞再看后评论,腰缠万贯财进门。
397 0
|
9月前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
414 0

热门文章

最新文章