前言

之前写了一篇关于《快速排序的4种优化》的博文,当时在验证各种情况的时候忽略内存分配的问题,导致所得到的结果分析的不全面。因为在刚开始写程序的时候将数组声明在 main() 里面,这样数组占用的栈空间,影响了递归的深度,也影响了程序处理的数据量(即使不用尾递归,处理的数据量也能超过 4 万)。在了解内存分配问题之前,先复习一下进程的概念。进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位(引入线程后,调度单位为线程)。简而言之,进程是程序的基本执行实体,是活的程序。程序和进程最大的差别为:是否获得了系统资源。进程会占用一定数量的内存,它可能是用来存放从磁盘载入的程序代码,也可能是存放取自用户输入的数据等。不过进程对这些内存的管理方式因内存用途不一而不尽相同,有些内存是事先静态分配和统一回收的,而有些却是按需要动态分配和回收的。

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

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

初始化数据段:通常将此段称为数据段,它用来存放可执行文件中需要明确赋初值的变量,换句话说就是存放程序静态分配的变量和全局变量。例如:int a = 100; 。

未初始化数据段:通常将此段称为 bss 段,它包含了程序中未初始化的全局变量。bss 是英文 Block Started by Symbol 的简称,bss 段属于静态内存分配。在程序开始执行之前,内核将此段中的数据初始化为 0 或空指针。例如:int sum[100]; 。// 此时,sum数组内的所有元素都为 0。

堆(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 节:用来存储未初始化的变量,如: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[]bss段
char *p1;     //p1在bss段(未初始化全局变量)
int main()
{int b;                   //b在栈区char s[] = "abc";        //s为数组变量,内容存储在栈区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;
}

2019.4.30补充

今天使用了一个函数 char *strtok_r(char *str, const char *delim, char **saveptr);,它的功能是切割字符串。传参的时候,我习惯性的创建char * str = " abc def "; 然后使用这个函数,结果程序报段错误。尝试修改为char str[ ] = " abc def "; 就可以解决问题。

这是因为这两种方式的操作对象不同。使用 char * str = "abc def" 后,编译器在内存的常量区分配一块内存,保存 "abc def" 这一字符串字面值,然后在栈上分配内存保存 str, str 的内容为 "abc def" 的地址。str 试图修改常量 "abc def" 的内容时(例如str[0] = 'g'),程序就崩了。而 char str[] = "abc def" 定义了一个数组,编译器为其在栈上分配了内存空间,因而可以进行修改操作。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>int main(void) {char source[] = "hello, world! welcome to china!";  //内容在栈区,可修改内容。char *input1 = source;                              //input1指向source,内容在栈区,所以input1可修改内容char *input2 = "hello, world! welcome to china!";   //input2所指内容在常量区,而input2本身在栈区//input2 = input1;(input2)[1] = 'a';                                  //尝试修改常量区的内容,程序出错printf("%c\n", (input1)[1]);                         printf("%c\n", (input2)[0]);                        //若只是读相关内容,程序能正常运行return 0;
}

malloc/free与new/delete的区别

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

不同点:

(1) 操作对象有所不同。
malloc 与 free 是 C/C++ 的标准库函数,new/delete 是 C++ 的运算符。对于非内部数据类的对象而言,光用 malloc/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 & freeObj*a = (Obj*)malloc(sizeof(obj));// allocate memorya->Initialize();                // initializationa->Destroy();                   // deconstructionfree(a);                        // release memory//use new & deleteObj*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

内存四区 malloc/free与new/delete的区别相关推荐

  1. 【C 语言】内存四区原理 ( 内存四区建立流程 )

    文章目录 一.内存四区建立流程 一.内存四区建立流程 内存四区 建立流程 : 1. 加载代码到内存 : 操作系统 中 执行 可执行程序 , 将 存放在硬盘中的 可执行程序 ( 包含代码 ) 加载到内存 ...

  2. 内存四区(代码区 静态区 栈区 堆区)

    参考:内存四区(代码区 静态区 栈区 堆区) 作者:今天天气眞好 发布时间: 2021-04-01 18:09:13 网址:https://blog.csdn.net/qq_51118175/arti ...

  3. c/c++的内存四区

    内存四区的图示 内存四区的代码案例 #include <stdio.h> void fun() {static int k = 10; //初始化的静态局部变量(data区的rw段)sta ...

  4. c/c++教程 - 2.1 程序的内存模型 内存四区 代码区 全局区 堆区 栈区 new操作符

    第2章为C++核心编程. 本阶段主要针对C++面向对象编程做详细讲解. 目录 1.内存分区模型 1.1 程序运行前(代码区.全局区) 1.2 程序运行后:(栈区.堆区) 1.3 new操作符(堆区内存 ...

  5. C 进阶内存四区(3)

    1 内存四区的建立流程 流程说明 1.操作系统把物理硬盘代码load到内存 2.操作系统把c代码分成四个区 3.操作系统找到main函数入口执行 2 内存四区模型和函数调用模型变量传递分析 1.一个主 ...

  6. C/C++之内存四区

    程序运行时,将内存大致分为四个区域 代码区:存放函数体的二进制代码,由操作系统进行管理的: 全局区:存放 全局变量和 静态变量以及 常量: 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等: ...

  7. alin的学习之路:C语言篇(一)(内存四区模型,宏函数,调用惯例,内存存储方式)

    @TOC(内存四区模型,宏函数,调用惯例,内存存储方式) 1. 内存四区及其使用注意 内存四区:代码区,全局静态区,栈区,堆区 代码区 代码区存放的是CPU执行的二进制指令 特点: 只读 共享 栈区 ...

  8. C语言程序的内存四区模型

    C语言程序的内存四区模型 内存四区的建立流程 流程说明 各区元素分析 内存四区的建立流程 流程说明 1.操作系统把物理硬盘代码load到内存 2.操作系统把c代码分成四个区 3.操作系统找到main函 ...

  9. 深入理解数据类型、变量类型属性、内存四区和指针

    数据类型可理解为创建变量的模具(模子):是固定内存大小的别名. 数据类型的作用:编译器预算对象(变量)分配的内存空间大小. 既能读又能写的内存对象,称为变量:若一旦初始化后不能修改的对象则称为常量. ...

最新文章

  1. 电子计算机说明文作文,电脑事物说明文
  2. 在Java 7里如何对文件进行操作
  3. Computer:现代计算机操作系统的四大基本特性(并发/共享/虚拟/异步)
  4. python pip工具命令_python 工具链 包管理工具 pip
  5. 小鹏汽车回应“侵犯消费者权益被罚3000元”:已于3月8日对购车协议内容进行调整...
  6. 这家中国企业和星巴克对着干 年亏16亿却成为全球最快上市公司
  7. javascript 滚动+停留 代码
  8. 安装flash player提示版本不是最新,无法安装
  9. 2019年考研篇(2020毕业)
  10. 【PC微信探秘】用易语言编写一个微信DLL注入器
  11. java Short详解
  12. Arun Jaitley:要健康最好让银行保持增长势头
  13. Word中如何连续使用格式刷
  14. 基于Spark框架的大型分布式矩阵求逆运算实现(二)——大型下三角矩阵求逆运算
  15. 区块链NFT之OpenSea
  16. lintcode----解码方法
  17. 已有oracle情况下重新安装oracle方法
  18. 不适定问题(ill-posed)
  19. 中国宠物药品行业研究及投资前景研究报告(2021版)
  20. Build2016两场Keynote的干货汇总

热门文章

  1. MFC下CSocket编程详解
  2. 计算机网络 | 传输层 :UDP与TCP协议详解
  3. Frida基础操作命令
  4. Python获取.wav音频的时长
  5. 每日一题之 MySQL
  6. 科普 | 5G基站功耗,到底有多可怕?
  7. MySQL的基本查询(二)
  8. 高效终端设备视觉系统开发与优化
  9. LiveVideoStackCon深圳 - VR/AR基础技术更成熟
  10. 来自曾经一起“挥洒汗水”的志愿者伙伴们的一封信