目录

一 C/C++内存分布

二 C语言中动态内存管理方式:malloc/calloc/realloc/free

内存分配方式与初始化

返回值与出错处理

应用场景

三 C++内存管理方式

1 new/delete操作内置类型

2 new和delete操作自定义类型

四. operator new与operator delete函数(重要点)

1 operator new与operator delete函数(重点)

五 new和delete的实现原理

1 内置类型 

2 自定义类型 

六 malloc/free和new/delete的区别


一 C/C++内存分布

大家知道,我们写好的东西叫做程序,程序需要编译,编译后的可执行程序运行起来了之后是以一个叫做进程的东西运行。

在硬件之上跑的软件叫做操作系统,操作系统可以根据一部分组件(如驱动)来控制硬件。操作系统之上有各种各样的程序,程序运行起来就叫做进程。

我们先来做一个例题来看看 内存分布:

全局数据,静态数据,局部的静态数据都在数据段

数组存放在栈上(数组名代表整个数组,但是数组名运算是对首元素的地址运算),所以*char,解引用是首元素的地址,数组在栈上,解引用也在栈上

pchar3在哪呢?有些同学可能会被前面的const所迷惑,但是这个const修饰的是指向的内容,不修饰本身,所以pchar3还是在栈上。*pchar3 解引用指向的内容是常量字符串,常量字符串在常量区。

这个时候有些同学可能有些蒙了:为什么char和pchar3解引用之后不一样了?我们来详细拆分一下:

num1和char都是数组,区别在于:第一个数组显示指定了大小,第二个数组是通过初始化确定大小(5个字节,有一个隐藏的/0)

而pchar是在栈上定义了一个局部的指针变量,这个指针变量保存的是字符串首元素的地址

常量区的数据特点:不能修改,修改的话运行会报错

ptr在栈上(指针变量ptr1是局部变量,存储在栈中),*ptr在堆上(ptr1通过malloc分配的内存来自堆)

  • globalVarC(数据段(静态区))。全局变量,存储在数据段。
  • staticGlobalVarC(数据段(静态区))。静态全局变量,存储在数据段。
  • staticVarC(数据段(静态区))。函数内的静态变量,存储在数据段。
  • localVarA(栈)。局部变量,存储在栈。
  • num1A(栈)。局部数组,存储在栈。
  • char2A(栈)。局部字符数组名,存储在栈。
  • *char2D(代码段(常量区))。char2 指向的字符串常量,存储在常量区。
  • pChar3A(栈)。局部指针变量,存储在栈。
  • *pChar3D(代码段(常量区))。pChar3 指向的字符串常量,存储在常量区。
  • ptr1A(栈)。局部指针变量,存储在栈。
  • *ptr1B(堆)。ptr1 通过 malloc 分配的内存,存储在堆。

所以答案是: C C C A A A A A D A B

【说明】
1. 栈又叫堆栈--非静态局部变量/函数参数/返回值等等,栈是向下增长的。 
2. 内存映射段是高效的I/O映射方式,用于装载一个共享的动态内存库。用户可使用系统接口
创建共享共享内存,做进程间通信。 
3. 堆用于程序运行时动态内存分配,堆是可以上增长的。 
4. 数据段--存储全局数据和静态数据。 
5. 代码段--可执行的代码/只读常量。

二 C语言中动态内存管理方式:malloc/calloc/realloc/free

void Test ()
{
 // 1.malloc/calloc/realloc的区别是什么?
 int* p2 = (int*)calloc(4, sizeof (int));
 int* p3 = (int*)realloc(p2, sizeof(int)*10);
 
 // 这里需要free(p2)吗?
 free(p3 );
}

malloccallocrealloc 都是 C 语言中用于动态内存管理的函数,它们主要有以下区别:

内存分配方式与初始化

  • malloc
    • 仅负责在堆内存中分配指定字节数的连续内存空间。例如 malloc(10 * sizeof(int)) 表示分配能容纳 10 个 int 类型数据的内存空间。
    • 分配后的内存空间内容是未初始化的,其中存储的值是不确定的垃圾值。如果需要使用这块内存存储数据,必须手动对其进行初始化。
  • calloc
    • 接受两个参数,第一个参数 num 表示要分配的元素个数,第二个参数 size 表示每个元素的字节大小,即分配 num * size 字节的连续内存空间。比如 calloc(5, sizeof(double)) 会分配能存放 5 个 double 类型数据的内存空间。
    • 会自动将分配的内存空间的每一位都初始化为 0,这对于需要初始化为 0 的场景(如初始化数组用于计数等)非常方便,无需再手动初始化。
  • realloc
    • 用于调整已经分配好的动态内存空间的大小。它的第一个参数 ptr 是之前通过 malloccalloc 或 realloc 分配的内存空间的指针,第二个参数 new_size 是调整后内存空间的大小
    • 当 ptr 为 NULL 时,realloc 的行为和 malloc(new_size) 一样,分配一块大小为 new_size 的内存空间;当 new_size 为 0 且 ptr 不为 NULL 时,realloc 会释放 ptr 指向的内存空间,相当于 free(ptr)
    • 对于调整后的内存空间,原来的数据会尽量保留(如果新的内存空间足够容纳原来的数据),但不会对新增的内存空间进行初始化 。

返回值与出错处理

  • malloc 和 calloc:成功时,都会返回一个指向分配内存起始地址的 void* 类型指针,由于 void* 可以隐式转换为其他类型指针,所以使用时通常会进行强制类型转换,如 (int*)malloc(...) 。当内存分配失败(比如系统内存不足)时,它们都会返回 NULL,因此在使用返回的指针之前,一定要检查其是否为 NULL,避免空指针引用错误。
  • realloc:成功时,返回调整后的内存空间起始地址的 void* 类型指针。如果在调整内存空间大小时发生错误(例如内存不足无法扩大内存),则返回 NULL,此时原来 ptr 指向的内存空间不会被释放,仍然有效 。如果调整成功,原来 ptr 指向的内存空间可能会发生变化(比如内存地址改变),所以通常要直接使用 realloc 的返回值更新原来的指针变量。

应用场景

  • malloc:适用于明确知道所需内存字节数,且不需要对内存进行自动初始化的场景,例如动态创建简单的数据结构节点,后续再手动设置节点内的数据 。
  • calloc:适合用于需要分配一块连续内存空间并将其初始化为 0 的场景,比如初始化数组用于存储统计信息、清零缓冲区等 。
  • realloc:当程序运行过程中需要根据实际情况动态调整已分配内存空间的大小时使用,比如根据实际读取的数据量来扩大存储数据的数组空间 。

扩容还分为:原地扩容和异地扩容

(1)原地扩容:扩容大小较小

(2)异地扩容:扩容大小较大

三 C++内存管理方式

C语言内存管理方式在C++中可以继续使用,但有些地方就无能为力,而且使用起来比较麻烦,因
此C++又提出了自己的内存管理方式:通过new和delete操作符进行动态内存管理。
 

1 new/delete操作内置类型

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

new后面的数据类型不一定必须是int,也可以是其他的类型

如果申请单个数据,则和ptr4一样。如果需要申请多个数据,则和ptr6一样(注意是[ ]括号)。

初始化在数据类型后加(),并在括号里加上要初始化的值

注意:申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用
new[]和delete[],注意:匹配起来使用。

2 new和delete操作自定义类型

对于类类型可以更方便的调用构造函数和析构函数

class A
{
public:
 A(int a = 0)
 : _a(a)
 {
 cout << "A():" << this << endl;
 }
 ~A()
 {
 cout << "~A():" << this << endl;
 }
private:
 int _a;
};
int main()
{
 // new/delete 和 malloc/free最大区别是 new/delete对于【自定义类型】除了开空间
还会调用构造函数和析构函数
 A* p1 = (A*)malloc(sizeof(A));
 A* p2 = new A(1);
 free(p1);
 delete p2;
 // 内置类型是几乎是一样的
 int* p3 = (int*)malloc(sizeof(int)); // C
 int* p4 = new int;
 free(p3);
 delete p4;
 A* p5 = (A*)malloc(sizeof(A)*10);
 A* p6 = new A[10];
 free(p5);
 delete[] p6;
 return 0;
}
  • malloc 与 free 操作

    • p1:通过 malloc 分配单个 A 类型大小的空间,不会调用构造函数
    • p5:通过 malloc 分配 10 个 A 类型大小的空间,不会调用构造函数
    • 释放时使用 free不会调用析构函数
  • new 与 delete 操作

    • p2:通过 new 创建单个 A 对象,会调用构造函数(传入参数 1)
    • p6:通过 new[] 创建 10 个 A 对象数组,会调用 10 次构造函数
    • 释放时使用 delete 和 delete[]会分别调用对应次数的析构函数

注意:在申请自定义类型的空间时,new会调用构造函数,delete会调用析构函数,而malloc与 free不会。

以后推荐使用new,delete,不推荐使用malloc

new和delete等方式失败都是抛异常,抛异常这个·内容讲解起来太繁杂,我们先在此处提一下,后面再讲解。

四. operator new与operator delete函数

new面对类对象的时候,有两步:(1)去堆上开辟新空间 (底层还是调用malloc,可以理解为new是malloc的再包装)   (2)调用析构函数

1 operator new与operator delete函数

operator new和operator delete不算是对new和delete的重载,而是一个全局函数

new和delete是用户进行动态内存申请和释放的操作符,operator new 和operator delete是
系统提供的全局函数,new在底层调用operator new全局函数来申请空间,delete在底层通过
operator delete全局函数来释放空间

new是先 operator new,再构造

delete是先调析构,再调operator delete(顺序不能变)

底层的代码如下:
 

/*
operator new:该函数实际通过malloc来申请空间,当malloc申请空间成功时直接返回;申请空间
失败,尝试执行空               间不足应对措施,如果改应对措施用户设置了,则继续申请,否
则抛异常。
*/
void *__CRTDECL operator new(size_t size) _THROW1(_STD bad_alloc)
{
 // try to allocate size bytes
 void *p;
 while ((p = malloc(size)) == 0)
     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;
}

通过上述两个全局函数的实现知道,operator new 实际也是通过malloc来申请空间,如果
malloc申请空间成功就直接返回,否则执行用户提供的空间不足应对措施,如果用户提供该措施
就继续申请,否则就抛异常。operator delete 最终是通过free来释放空间的

五 new和delete的实现原理

1 内置类型 


如果申请的是内置类型的空间,new和malloc,delete和free基本类似,不同的地方是:
new/delete申请和释放的是单个元素的空间,new[]和delete[]申请的是连续空间,而且new在申
请空间失败时会抛异常,malloc会返回NULL。
 

2 自定义类型 


new的原理
1. 调用operator new函数申请空间
2. 在申请的空间上执行构造函数,完成对象的构造
 #define   free(p)               _free_dbg(p, _NORMAL_BLOCK)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/free和new/delete的区别

malloc/free和new/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在释放空间前会调用析构函数完成空间中资源的清理释放

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐