内存分配&&malloc/free与new/delete的区别

前言

在了解内存分配问题之前,先复习一下进程的概念。进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位(引入线程后,调度单位为线程)。简而言之,进程是程序的基本执行实体,是活的程序。程序和进程最大的差别为:是否获得了系统资源。进程会占用一定数量的内存,它可能是用来存放从磁盘载入的程序代码,也可能是存放取自用户输入的数据等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的。

对任何一个普通进程来讲,它都会涉及到5种不同的数据段(如代码段,数据段,BSS段,堆段,栈段)。

代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作。其通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的变量,例如字符串常量等。

静态存储区:数据段用来存放可执行文件中已初始化全局变量,换句话说就是存放程序静态分配的变量和全局变量。BSS段存储未初始化的全局变量、静态变量。

BSS段:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零。BSS段(bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc()或new分配内存时,新分配的内存就被动态添加到堆上;当利用free()或delete释放内存时,被释放的内存从堆中被剔除。堆区由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意:它与数据结构中的堆是两回事,分配方式类似于链表。(C++ Primer Plus中文版356页说new创建的对象将驻留在栈内存是翻译者的错。)

:栈又称堆栈,栈是用户存放程序临时创建的局部变量,也就是说我们函数括弧“{}”中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的后进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。栈区由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。它是由操作系统分配的,内存的申请与回收都由OS管理。

在进程被载入内存中时,基本上被分裂成许多小的节(section)。需要关注的是6个主要的节:

(1).text 节:基本上相当于二进制可执行文件的.text部分,它包含了完成程序任务的机器指令。该节标记为只读,如果发生写操作,会造成segmentation fault。在进程最初被加载到内存中开始,该节的大小就被固定。

(2).data 节:用来存储初始化过的变量,如:int a =0;该节的大小在运行时固定的。

(3).bss 节:栈下节(belowstack section ,即.bss)用来存储为初始化的变量,如:int a; 该节的大小在运行时固定的。

(4)堆节(heapsection):用来存储动态分配的变量,位置从内存的低地址向高地址增长。内存的分配和释放通过malloc() 和 free() 函数控制。

(5)栈节(stacksection):用来跟踪函数调用(可能是递归的),在大多数系统上从内存的高地址向低地址增长。同时,栈这种增长方式,导致了缓冲区溢出的可能性。

(6)环境/参数节(environment/argumentssection):用来存储系统环境变量的一份复制文件,进程在运行时可能需要。例如,运行中的进程,可以通过环境变量来访问路径、shell 名称、主机名等信息。该节是可写的,因此在格式串(format string)和缓冲区溢出(buffer overflow)攻击中都可以使用该节。另外,命令行参数也保持在该区域中。

之前,我对这类知识不太关注,只是知道个大概,但是,最近的写的快排优化,让我意识到这类问题的重要性(也是对知识盲区的学习)。在哪一篇博文中,我习惯性的将数组声明写在了main()函数里。从而得出了在Codeblocks里使用固定基准方式(没有尾递归的情况)处理升序数组时只能处理4万个数组元素的结论(这一结论并不能说错,但忽略了内存分配的问题)。因为我之前并不太清楚main函数中的数组(不是动态分配)占用的是栈的空间。所以这就影响了递归的深度。准确的说,要是将数组声明成全局的,那个算法就可以处理更多的数据元素。上面的概念我们已经学习了,简化其复杂的内容,可以将内存的分配当做只有4个部分(代码区、静态区、堆和栈)。现在就用一个简单的例子来看看各种变量在内存中的分配情况:

#include <stdio.h>
 
int a = 0;    //a在全局已初始化数据区  
int asd[10];  //asd[]在静态区
char *p1;     //p1在BSS段(未初始化全局变量)  
int main()  
{
    int b;                   //b在栈区
    char s[] = "abc";        //s为数组变量,存储在栈区,"abc"为字符串常量,存储在已初始化数据区
    char *p1,p2;            //p1、p2在栈区
    char *p3 = "123456";     //123456\0在已初始化数据区,p3在栈区  
    static int c = 0;       //C为全局(静态)数据,存在于已初始化数据区,另外,静态数据会自动初始化
    p1 = (char*)malloc(10);  //分配得来的10个字节的区域在堆区
    p2 = (char*)malloc(20);  //分配得来的20个字节的区域在堆区
    free(p1);
    free(p2);
    return 0;
}


 

malloc/free与new/delete的区别

相同点:都可用于申请动态内存和释放内存

不同点

(1)操作对象有所不同。
malloc与free是C/C++的标准库函数,new/delete 是C++的运算符。对于非内部数据类的对象而言,光用maloc/free 无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象消亡之前要自动执行析构函数。由于malloc/free 是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加malloc/free。

(2)用法上也有所不同。
函数malloc 的原型如下:

void* malloc(size_t size);


用malloc 申请一块长度为length的整数类型的内存,程序如下:

int *p = (int*)malloc(sizeof(int) * length);


注意:①malloc 返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
②malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
函数free 的原型如下:

void free(void* memblock);


为什么free 函数不象malloc 函数那样复杂呢?原因是指针p 的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p 是NULL 指针,那么free对p 无论操作多少次都不会出问题。如果p不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误。


new/delete 的使用要点

运算符new 使用起来要比函数malloc 简单得多,例如:

int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];


这是因为new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new 的语句也可以有多种形式。
如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如:

Obj *objects = new Obj[100];     // 创建100 个动态对象
Obj *objects = new Obj[100](1);  // 错误写法,VS下直接报无参数构造函数


在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如

delete []objects; // 正确的用法
delete objects;   // 错误的用法


后者相当于delete objects[0],漏掉了另外99 个对象。

(1)new自动计算需要分配的空间,而malloc需要手工计算字节数
(2)new是类型安全的,而malloc不是,比如:

int*p=new float[2];              //编译时指出错误
int*p=malloc(2*sizeof(float));   //编译时无法指出错误


new operator由两步构成,分别是operator new和construct。
(3)operator new对应于malloc,但operator new可以重载,可以自定义内存分配策略,甚至不做内存分配,甚至分配到非内存设备上。而malloc无能为力。
(4)new将调用constructor,而malloc不能;delete将调用destructor,而free不能。
(5)malloc/free要库文件支持,new/delete则不要。

本质区别

malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符。对于用户自定义的对象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。

#include <iostream>
#include <malloc.h>
using namespace std;
 
class Obj
{
public:
    Obj( )
    { cout  <<  "Initialization"  <<  endl; }
    ~ Obj( )
    { cout  <<  "Destroy" <<  endl; }
    void Initialize( )
    { cout  <<  "Initialization"  <<  endl; }
    void  Destroy( )
    { cout  <<  "Destroy"  <<  endl; }
}obj;
 
int main()
{
    Obj *objects = new Obj[10];
    cout <<endl;
 
    //use malloc & free
    Obj*a=(Obj*)malloc(sizeof(obj));// allocate memory
    a->Initialize();                // initialization
    a->Destroy();                   // deconstruction
    free(a);                        // release memory
 
    //use new & delete
    Obj*b=new Obj;
    delete b;
    return 0;
}


问题:既然new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?

答:因为C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete、malloc/free必须配对使用。

参考:https://blog.csdn.net/hackbuteer1/article/details/6789164
 

猜你喜欢

转载自blog.csdn.net/weixin_41066529/article/details/89431403