【系统】C/C++内存管理之内存分配

2023-11-18

文章目录


0)内存分配方式

内存分配方式有三种:

  • 从静态存储区域分配:

    内存在程序编译的时候就已经分配好了,这块内存在程序的整个运行期间都存在。
    • 例如全局变量,static静态成员变量
  • 在栈上创建:

    执行函数时,函数内部变量的存储单位可以在栈上创建。
    • 函数执行结束时,这些存储单元自动释放。
    • 栈内存分配运算置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  • 在堆上分配:

    也称为动态内存分配
    • 程序在运行的时候用malloc或new申请任意多少内存,程序员自己负责在何时用free或delete来释放这块内存。
    • 动态内存的生命周期由程序员决定,使用非常灵活。
    • 但如果在堆上分配了空间,就有责任回收它,否则运行的程序会出现泄漏,频繁地分配和释放不同大小的堆空间将会产生堆内碎块。也就是我们常说的内存碎片。

*程序内存空间

一个程序将操作系统分配给其运行的内存分为5个区域:

  • 栈区(stack area):由编译器自动分配释放,存放为函数运行的局部变量,函数参数,返回数据,返回地址等。操作方式与数据结构中的类似。
  • 堆区(heap area):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。分配方式类似于链表。
  • 全局数据区(data area):也叫做静态区,存放全局变量,静态数据。程序结束后由系统释放。
  • 程序代码区(code area):存放函数体的二进制代码。但是代码段中也分为代码段和数据段。
  • 文字常量区:可以理解为常量区,常量字符串存放这里。程序结束后由系统释放。字常量是不可寻址的


1)C语言内存分配方式

在C语言中,对象可以使用静态或动态的方式分配内存空间

  • 静态分配:编译器在处理程序源代码时分配。
    • 静态内存分配是在程序执行之前进行的因而效率比较高
  • 动态分配:程序在执行时调用malloc库函数申请分配
    • 动态内存分配则可以灵活地处理未知数

*静态与动态内存分配区别

  • 静态对象是有名字的变量,可以直接对其进行操作;动态对象是没有名字的一段地址需要通过指针间接地对它进行操作
  • 静态对象的分配与释放由编译器自动处理;动态对象的分配与释放必须由程序员显式地管理,它通过malloc()和free两个函数来完成。

1.1 静态分配方式

int a = 100;
  • 此行代码指示编译器分配足够的存储区以存放一个整型值,该存储区与名字a相关联,并用数值100初始化该存储区。

1.2 动态分配方式

动态分配内存的定义是这样的,指在程序运行过程中,要申请内存,系统会根据程序的实际情况来分配,分配空间的大小是由程序的需求来决定的。

p1 = (char *)malloc(10*sizeof(int));
  • 此行代码分配了10个int类型的对象,然后返回对象在内存中的地址,接着这个地址被用来初始化指针对象p1。

  • 对于动态分配的内存唯一的访问方式是通过指针间接地访问,其释放方法为:

    free(p1);
    

在C语言下面,举个例子,定义一个指针,int *p;

  • 此时指针 i 是一个野指针,是一个指向不确定位置的指针,对它进行操作是很危险的,此时我们需要动态分配内存空间,让 i 指向它。
  • 而有一种形式是这样的,int *p=&b,这并非是一种动态内存分配方式,而是一种指针的初始化,把变量b的首地址给了指针p。

C语言下供了几个函数来实现动态内存分配,分别是malloc()、calloc()、realloc(),而释放内存的函数为free()。

1、malloc函数

函数原型为void *malloc(unsigned int size);

  • 在内存的动态存储区中分配一块长度为"size" 字节的连续区域
  • 函数的返回值为该区域的首地址
  • “类型说明符”表示把该区域用于何种数据类型
  • (类型说明符*)表示把返回值强制转换为该类型指针
  • “size”是一个无符号数。

例如: pc=(char *) malloc (100); 表示:

  • 分配100个字节的内存空间
  • 并强制转换为字符数组类型
  • 函数的返回值为指向该字符数组的指针
  • 把该指针赋予指针变量pc
  • 若size超出可用空间,则返回空指针值NULL。

2、calloc 函数

函数原型为void *calloc(unsigned int num, unsigned int size)

  • 按所给数据个数和每个数据所占字节数开辟存储空间。
  • 其中num为数据个数,size为每个数据所占字节数,故开辟的总字节数为num*size。
  • 函数返回该存储区的起始地址。
  • calloc函数与malloc 函数的区别仅在于一次可以分配n块区域

例如: ps=(struct stu*) calloc(2,sizeof (struct stu));

  • 其中的sizeof(struct stu)是求stu的结构长度。
  • 因此该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps。

3、realloc函数

函数原型为void *realloc(void *ptr, unsigned int size)

  • 重新定义所开辟内存空间的大小
  • 其中ptr所指的内存空间是用前述函数已开辟的,size为新的空间大小,其值可比原来大或小。
  • 函数返回新存储区的起始地址(该地址可能与以前的地址不同)。

例如p1=(float *)realloc(p1,16); 将原先开辟的8个字节调整为16个字节。

4、free函数

函数原型为void free(void *ptr)

  • 将以前开辟的某内存空间释放
  • 其中ptr为存放待释放空间起始地址的指针变量,函数无返回值。
  • 应注意:ptr所指向的空间必须是前述函数所开辟的。
  • 注意:动态申请的内存空间要进行手动用free()函数释放。
    • malloc、calloc、realloc都是在堆上分配的,堆上分配的空间必须由用户自己来管理。

例如free((void *)p1); 将上例开辟的16个字节释放。

  • 可简写为free(p1);,由系统自动进行类型转换。


2)C++语言动态内存分配

C++语言中用new和delete来动态申请和释放内存。

2.1 申请

new 是个操作符。

运算符new使用起来要比函数malloc简单得多。

  • 这是因为new内置了sizeof、类型转换和类型安全检查功能
  • 对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。
  • 申请单个对象:

    int *p;
    p=new int;	//或者 p=new int(value);
    

    new内部的调用顺序:(初始化一个对象时):new-->operator new-->malloc-->构造函数

  • 动态申请数组:

    int *p;
    p=new int [100];	//这样可以申请长度为100的数组,但是不能进行初始化。
    

    new内部的调用顺序:(初始化若干个对象时):new-->operator new[]-->operator new-->malloc-->构造函数

2.2 释放

int *p, *q;
p=new int;
q=new int[10];
delete p;
delete []q;
  • delete单个对象时,调用顺序为:
    delete-->析构函数-->operator delete-->free
  • delete多个对象时,调用顺序为:
    delete-->析构函数-->operator delete[]-->operator delete-->free


3)new/delete与malloc/free

联系:

  • 它们都是动态管理内存的入口。
  • new/delete的底层调用了malloc/free。

区别:

  • malloc/free是C/C++标准库的函数,new/delete是C++操作符。
  • new 建立的是一个对象,会调用构造函数,malloc分配的是一块内存。
    • malloc/free只是动态分配内存空间/释放空间。
    • 而new/delete除了分配空间,还会调用构造/析构函数进行初始化与清理(清理成员)。
  • malloc/free需要手动计算类型大小且返回值为void*,new/delete可自己计算类型的大小对应类型的指针。
  • malloc/free申请空间后得判空,new/delete则不需要。
  • new直接跟类型,malloc跟字节数个数。

*有了malloc/free为什么还要new/delete?

malloc与free是C /C语言的标准库函数,new/delete是C 的运算符。它们都可用于申请动态内存和释放内存

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

因此需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。



4)常见的内存错误及其对策

发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。

  • 内存分配未成功,却使用了它:

    常用解决办法:在使用内存之前检查指针是否为NULL

    • 如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。
    • 如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。

  • 内存分配虽然成功,但是尚未初始化就引用它:

    • 犯这种错误主要有两个起因:
      • 一是没有初始化的观念;
      • 二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。
    • 内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。
    • 所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

  • 内存分配成功并且已经初始化,但操作越过了内存的边界:

    • 例如在使用数组时经常发生下标“多1”或者“少1”的操作。
    • 特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

  • 忘记了释放内存,造成内存泄露:

    • 含有这种错误的函数每被调用一次就丢失一块内存。
      • 刚开始时系统的内存充足,你看不到错误。
      • 终有一次程序突然死掉,系统出现提示:内存耗尽。
    • 解决方法:动态内存的申请与释放必须配对,程序中malloc与free的使用次数一定要相同,否则肯定有错误(new/delete同理)。

  • 释放了内存却继续使用它:

    • 有三种情况:

      • 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存。
        • 此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

      • 函数的return语句写错了
        • 注意不要返回指向“栈内存”的“指针”或者“引用”,因为该内存在函数体结束时被自动销毁。

      • 使用free或delete释放了内存后,没有将指针设置为NULL,导致产生“野指针”

    • 解决方法

      • 【规则1】用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
      • 【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
      • 【规则3】避免数组或指针的下标越界,特别要当心发生“多1”或者“少1”操作。
      • 【规则4】动态内存的申请与释放必须配对,防止内存泄漏。
      • 【规则5】用free或delete释放了内存之后,立即将指针设置为NULL,防止产生“野指针”。


5)指针与数组的对比

  • 数组:数组是用于储存多个相同类型数据的集合。
  • 指针:指针相当于一个变量,但是它和不同变量不一样,它存放的是其它变量在内存中的地址

1、赋值

  • 同类型指针变量可以相互赋值。
  • 数组不行,只能一个一个元素的赋值或拷贝

2、存储方式

  • 数组:数组在内存中是连续存放的,开辟一块连续的内存空间。

    • 数组是根据数组的下标进行访问的,多维数组在内存中是按照一维数组存储的,只是在逻辑上是多维的。
    • 数组要么在静态存储区被创建(如全局数组),要么在上被创建。
    • 数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。
  • 指针:指针很灵活,它可以指向任意类型的内存块

    • 指针的类型说明了它指向地址空间的内存。
    • 由于指针本身就是一个变量,再加上它所存放的也是变量,所以指针的存储空间不能确定

3、求sizeof

数组:

  • 数组所占存储空间的内存:sizeof(数组名)
  • 数组的大小:sizeof(数组名)/sizeof(数据类型)

指针:

  • 在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4。
  • 在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。

4、初始化

数组:

1char a[]={"Hello"};//按字符串初始化,大小为62char b[]={'H','e','l','l'};//按字符初始化(错误,输出时将会乱码,没有结束符)3char c[]={'H','e','l','l','o','\0'};//按字符初始化

指针:

//(1)指向对象的指针:(()里面的值是初始化值)
int *p=new int(0) ;    delete p;
//(2)指向数组的指针:(n表示数组的大小,值不必再编译时确定,可以在运行时确定)
int *p=new int[n];    delete[] p;
//(3)指向类的指针:(若构造函数有参数,则new Class后面有参数,否则调用默认构造函数,delete调用析构函数)
Class *p=new Class;  delete p;
//(4)指针的指针:(二级指针)
int **pp=new (int*)[1]; 
pp[0]=new int[6];
delete[] pp[0];


【部分内容参考自】

  • 深入理解C语言内存管理:https://www.cnblogs.com/jack-hzm/p/11545026.html
  • C+±内存管理:https://blog.csdn.net/skrskr66/article/details/92769994
  • c++详解【new和delete】:https://blog.csdn.net/xxpresent/article/details/53024555
  • C和C++ 语言动态内存分配:https://www.cnblogs.com/zhj202190/archive/2011/05/11/2043620.html
  • 数组和指针的区别与联系(详细):https://blog.csdn.net/cherrydreamsover/article/details/81741459
  • C 内存管理详解:https://blog.csdn.net/ysdaniel/article/details/6643689
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【系统】C/C++内存管理之内存分配 的相关文章

  • C 中的复合语句表达式

    下面的代码不起作用 int i void 999 100 添加括号就可以了 为什么 int i void 999 100 还有另一种方法可以完成此类分配 int i void 999 100 是什么让他们与众不同 在这份声明中 int i
  • 隐式方法组转换陷阱

    我想知道为什么给定代码的输出 在 LinqPad 中执行 void Main Compare1 Action Main Dump Compare2 Main Dump bool Compare1 Delegate x return x Ac
  • 如何“杀死”Pthread?

    我正在学习 Pthreads 并且想知道杀死这样一个对象的最佳方法是什么 在寻找类似的问题后 我无法找到 明确 的答案 但请随时向我指出任何相关问题 我正在使用一个小型客户端服务器应用程序 其中服务器主线程正在侦听套接字上的客户端连接 每次
  • 使用静态类型代替变量

    当您的项目不使用命名空间时 有什么方法可以告诉编译器使用静态类型而不是变量吗 例如 我有一个名为 User 的类 它具有各种静态和非静态方法 假设调用了其中一个静态方法GetUser 我想称之为User GetUser 方法来自一个方法 该
  • 如何从 List 中的字符串中删除数字/数字?

    我有一个字符串列表 List
  • C++ 私有静态成员变量

    此 C 代码在编译时产生链接器错误 A h class A public static void f private static std vector
  • 'goto *foo' 其中 foo 不是指针。这是什么?

    我正在玩标签作为值 https gcc gnu org onlinedocs gcc Labels as Values html并最终得到这段代码 int foo 0 goto foo 我的 C C 经验告诉我 foo means dere
  • 如何在 C++ 中对静态缓冲区执行字符串格式化?

    我正在处理一段对性能要求非常高的代码 我需要执行一些格式化的字符串操作 但我试图避免内存分配 甚至是内部库的内存分配 在过去 我会做类似以下的事情 假设是 C 11 constexpr int BUFFER SIZE 200 char bu
  • 正则表达式删除某些字符周围不需要的空格

    我正在尝试从 JavaScript 文件中删除一些不需要的空格 并在将文件发送到客户端之前使用 C 和 Regex 组合文件 我有一个JavascriptHandler处理 js 文件 效果很好 这是我用来 打包 JavaScript 的函
  • 使用成员作为实现者来实现接口

    我有实现 IA 的 A 类 现在我需要创建也应该实现 IA 的类 B B 类有 A 类的实例作为成员 有什么方法可以定义A的实例实现B类中的IA吗 interfase IA void method1 void method2 void me
  • 套接字:监听积压并接受

    listen sock backlog 在我看来 参数backlog限制连接数量 这是我的测试代码 server initialize the sockaddr of server server sin family AF INET ser
  • 如何用C++解析复杂的字符串?

    我试图弄清楚如何使用 解析这个字符串sstream 和C 其格式为 string int int 我需要能够将包含 IP 地址的字符串的第一部分分配给 std string 以下是该字符串的示例 std string 127 0 0 1 1
  • 使用互斥锁来阻止临界区外部的执行

    我不确定我的术语是否正确 但这里是 我有一个由多个线程使用的函数来写入数据 在注释中使用伪代码来说明我想要的内容 these are initiated in the constructor int data std atomic
  • 如何解释“错误C2018:未知字符'0x40'?[关闭]

    Closed 这个问题需要调试细节 help minimal reproducible example 目前不接受答案 在编译一些代码时 我收到以下信息 错误 C2018 未知字符 0x40 我想知道如何解决这样的问题 这是我要开始的地方
  • 我应该使用多个 HttpClient 来进行批量异步 GET 请求吗?

    我有一个场景 我需要在尽可能短的时间内发出大量 GET 请求 想想大约 1000 个 我知道通常最好保留一个客户端并尽可能重用它 Create Single HTTP Client HttpClient client new HttpCli
  • double 类型的静态类成员的常量表达式初始值设定项

    在 C 11 和 C 14 中 为什么我需要constexpr在下面的代码片段中 class Foo static constexpr double X 0 75 而这会产生编译器错误 class Foo static const doub
  • 将小数格式化为两位或整数

    对于 10 我想要 10 而不是 10 00 对于 10 11 我想要 10 11 没有代码可以实现吗 即通过指定格式字符串类似于 0 N2 decimal num 10 11M Console WriteLine num ToString
  • C/C++ 通过 Android NDK 在 JNI 中看不到 Java 方法

    我正在尝试从使用 NDK 构建的 C 类文件调用 Java 方法 它不断抛出常见的 未找到非静态方法 错误并导致整个 Android 应用程序崩溃 下面的代码片段 有些东西可能不需要 但我按原样保留它们 因为焦点 问题在于refreshJN
  • 编译器可以报告未知属性的错误吗?即使有范围?

    在N3291 7 6 1 3 5 属性语法和语义 decl attr grammar 关于如何属性是用我读过的源代码写的 使用一个属性范围令牌是有条件支持的 实现定义的行为 and For an 属性标记本国际标准中未指定 该行为是实现定义
  • 具有多种类型的 C# 泛型类型推断

    我有以下通用方法 用于将一种类型的输入对象序列化为超类型 如下所示 public string SerialiseAs

随机推荐

  • 程序员秋招最全Java面试题及答案整理(2023最新版)

    前言 大家好 最近一个月 花了不少时间 给大家整理了一套 2023 的技术面试资料 包括各大厂最新面试题以及面经解析涉及JVM Mysql 并发 Spring Mybatis Redis RocketMQ Kafka Zookeeper N
  • 【C刷题】day1

    一 选择题 1 正确的输出结果是 int x 5 y 7 void swap int z z x x y y z int main int x 3 y 8 swap printf d d n x y return 0 答案 3 8 解析 考
  • 怎样将excel文件导入mysql中

    1 整理好excel表中的字段 2 在Navicat中创建表 如果导入的是一个追加的表 则无需创建新表 CREATE TABLE orderinfo orderid VARCHAR 10 NULL 订单 id 主键 userid INT 1
  • 华为OD机试2023年最新题库(JAVA、Python、C++)

    我是一名软件开发培训机构老师 我的学生已经有上百人通过了华为OD机试 学生们每次考完试 会把题目拿出来一起交流分享 重要 5 11月份考的都是OD统一考试 B卷 2023年5月份题库已经更新为OD统一考试 B卷 题库由三部分组成 1 202
  • 【H5】 svg内text、image、path标签的使用

    H5 svg内text image path标签的使用 text标签 div style width 500px height 500px border 2px solid pink margin 50px auto 0 div
  • XML中约束文档的引用和书写

    在XML中定义了一套规则 来对文档内容进行约束 这叫做XML约束 常用的俩种约束语言 DTD约束 Schema约束 XML文档中可以引入多个约束文档 为了防止出现不同含义的同名名称冲突 所以 所以可以XML提供了名称空间 1 DTD语法 D
  • 【HTML】列表标签、表格标签、块级标签、表单标签

    文章目录 一 列表标签 1 无序列表 2 无序列表 3 定义列表 项目列表 二 表格标签 1 表格整体架构 2 表格的标签介绍 3 table标签的属性 4 tr标签的属性 5 th td标签的属性 6 跨行 跨列的表格 三 块级标签 1
  • vue猜数字游戏

    div p msg p div
  • node.js JSON对象和string的相互转化

    JSON stringify obj 将JSON转为字符串 var json aa sdddssd bb 892394829342394792399 23894723984729374932874 cc 11111111111111 gt
  • html5期末知识点归纳总结,web期末考试知识点

    题型及知识点 一 知识点 上课内容全覆盖 除补充的html5和css3的内容 常用的html标记及属性 弄清楚哪些是块元素 哪些是行内元素 特殊字符标记 p40 Css属性 i 字体 font size font family font w
  • 蓝桥杯JAVA B组 2020(1)第五题 排序

    一 题目描述 小蓝最近学习了一些排序算法 其中冒泡排序让他印象深刻 在冒泡排序中 每次只能交换相邻的两个元素 小蓝发现 如果对一个字符串中的字符排序 只允许交换相邻的两个字符 则在所有可能的排序方案中 冒泡排序的总交换次数是最少的 例如 对
  • SQLCipher核心思想

    加密原理 page data iv hmac iv是一段随机数 可以保证每一页的iv值都不一样 和page data一起作用 用于生成hmac值 sizeof page data p
  • kubeasz 二进制安装k8s高可用集群

    一 kubeasz介绍 项目致力于提供快速部署高可用k8s集群的工具 同时也努力成为k8s实践 使用的参考书 基于二进制方式部署和利用ansible playbook实现自动化 既提供一键安装脚本 也可以根据安装指南分步执行安装各个组件 二
  • Maven2部署构件到Nexus时出现的Failed to transfer file错误

    原文出处 http www javatang com archives 2010 01 23 4518375 html 作者 Jet Mah from Java堂 声明 可以非商业性任意转载 转载时请务必以超链接形式标明文章原始出处 作者信
  • jni基础

    JNI相关 静态注册 java代码需要和C 代码相符通讯就需要通过JNI来进行注册 java public native String stringFromJNI 代表该函数的实现在so so Java com first firstndk
  • 内存管理方案

    内存管理方案 Memory Management System Author Owen 目 录 内存管理方案 1 目 录 1 1 概述 2 2 理论依据 2 2 1 不对内存进行管理 2 2 2 对内存进行内部管理 2 3 实现方案 2 3
  • RMI-Remote Method Invocation远程方法调用的使用

    手动启动服务方式 参考自 https www cnblogs com fanghao p 8918953 html 接口 import java rmi Remote import java rmi RemoteException publ
  • selenium二次封装

    使用到相关技术 selenium testng log4j maven selenium主要生成driver 获取页面元素等 testng主要Run程序 生成测试报告 管理用例执行顺序 断言等 log4j主要用来打印log日志 maven主
  • RuntimeError: CUDA unknown error - this may be due to an incorrectly set up environment

    解决方案 输入指令sudo shutdown r now即可重新启动驱动 如果还是无法解决则需要重新安装驱动
  • 【系统】C/C++内存管理之内存分配

    文章目录 0 内存分配方式 从静态存储区域分配 在栈上创建 在堆上分配 程序内存空间 1 C语言内存分配方式 静态与动态内存分配区别 1 1 静态分配方式 1 2 动态分配方式 1 malloc函数 2 calloc 函数 3 reallo