当前课程知识点:基于Linux的C++ > 第七讲 指针与引用 > 7.7 动态存储管理(一) > LinuxCPP0707
接下来一节就是我们的动态存储管理
这个涉及到非常复杂的
动态内存分配的任务
标准库里边
C架构提供了内存分配函数malloc
和内存释放函数free
它还提供了C++的内存分配管理操作符
new和delete
new是内存分配 delete是删除
首先我们来看内存分配与释放
在我们的C++的程序代码中
内存分配或释放
实际上是有三种内存管理方式
一个是静态的内存分配
正常情况下
所有的全局数据对象和静态的局部量
都适用我们的静态内存分配的模式
在程序运行前分配好
程序结束了它就释放
也就是说你按照静态的方式分配这些内存
它的生存期是巨大的
它就和我们的程序一样得长
实际上 比我们真正的main函数运行还要长
它在main函数之前就能分完
main函数做完了它才能够销毁
第二种就是自动内存分配
它的使用对象就是普通的局部数据对象
什么时候分配呢
你的程序流程什么时候进入这个函数块
或者这个复合语句块
才为它分配内存 离开了它就销毁
第三种 今天要讲的动态内存分配
你想让它什么时候分配就什么时候分配
你想让它什么时候销毁就什么时候销毁
全由程序员编程说了算
编程 你要分配它 就调用malloc
或者使用new操作符
你要销毁它 就调用free
或者使用delete操作符
这两种方案都可以
一种是C的方案 一种是C++方案
那么我们为什么需要动态内存分配
静态的和自动的这个内存分配方式
必须事先了解这个数据对象的格式
和它的存储空间的大小
你不了解这一点
编译器没有办法替你分配
刚好合适的存储空间
但是有些时候 我们没有办法事先决定
这个数据对象的大小
静态分配和自动分配这个事情就做不了
你比如讲 我要你声明包含n个元素的
一个整数数组
n的值由用户在程序运行的时候输入
你怎么办呢 程序员你写这个程序
别人要用你的程序
然后他会分配一个n个元素的整数数组
n值什么时候来呢
他运行这个程序 输入一个元素个数
他就给你输出一个元素个数
然后你就分配那么多个元素
那你的程序在写的时候
你不知道用户在执行的时候
他会输入什么样的值啊
你分配两个元素 不够
三个怎么办 冒了
一冒了怎么办 程序就崩了
十个 够吗 不够
250够吗 不够
1万够吗 不够
多少它都不够
就是说当你碰到这种问题的时候
静态和自动两种分配方式
解决不了这个问题
因为什么时候你都没有办法预知
用户输入数据n值到底是多少 不能精确
你就没有办法给它分配恰当的内存
你说我尽可能得多
那没完没了一个事情
你不可能尽可能的
因为你不管给我多个数
我总能找到一个比它更大的数
所以说你必须能够恰当地分配
这才合适
不这么做 它就不恰当 那怎么办
你编译的时候程序是没有执行的
你压根不知道那个值
所以静态的不成 自动也不成
那我们怎么办 我们就要动态分配
只有动态分配才能够刚好地
给我们解决这个问题
你想分多大 我就给你多大
你要多少 给你多少
只要我们内存是足够的
那我就能够给你分
所有这些动态内存分配
都在一个专门的存储区
我们称它为堆的这样一个里面
它会涉及到两个主要的关键技术
一个我们会使用一个指针
来指向动态分配的内存区域
第二个就要使用引领操作符
来操纵目标数据对象
也就是说 没有指针
这个动态内存分配是做不到的
那我们在C里面
怎么对它进行动态内存分配呢
C里面提供这个函数malloc和free
就可以完成我们的动态内存分配
这两个函数原型定义在“cstdlib”和“cmalloc”
这两个头文件里边
这两个头文件里边你包含一个就够了
分配是malloc 释放是free
相应的分配函数不是malloc一个
是提供了好几个
我们总常用的是malloc
它会接受一个参数
就表示你分配多少个字节
size 你就传给它
你想分配5个字节
OK 你就传5个进去
你要分配4个字节
你就传4进去
你说我分配一个整数
整数多大呢 4个字节 也不一定
我们用的现在主流计算机
都是64位计算机 不是32位计算机
但是我们的主流编译器
还是32位编译器
所以分配一个整数就是4个字节
你也不能想当然总是4个字节
所以你要想确保能够正确的分配
sizeof(int)传进去 它是以字节为单位
你分配多少个字节 然后它就传回给你
它给你分配好的动态内存区域的基地址
free 就销毁它
你这么动态内存分配的一个区域
后来你又不要了
不要了 你就把那个基地址
通过memblock这个指针传进去
那个目标区域 就被你销毁掉了
你注意看 我们这里面
非常重要的一个指针类型 void *
这是一个非常非常非常重要的数据类型
我称它为哑型指针 它非常特殊
它表示我们的目标数据对象类型
是未知的 它是一个指针没错
指针的目标数据对象类型void也没错
不是说它的目标数据对象没有类型
目标数据对象一定是有类型的
但是它的类型是什么呢 我不知道
所以你绝不可以在这个指针上边
通过引领操作符
去访问它的目标数据对象
但是你可以把它转换成
指向其它类型的一个指针
转型完了以后
再去访问它的目标数据对象
这就可以了
但是转型操作是不是对的
那你程序员得保证它是对的
你如果保证不了 你这个程序就写错了
它的最主要的目的
就是作为一种通用的指针类型
首先 你可以用它来构造一个
指针数据对象和我们的目标数据对象的
一个一般性的关联
我就构造它 我用一个指针
我指向一个东西 那个东西类型是什么
我现在还不知道
OK我就用void *指针 然后指向它
后来我知道了它是什么类型
或者我需要详细地刻画
它到底是什么数据类型 比如是int *,是个char *
OK 我就把这个void *指针转换成int *
或者转换成char *
然后我就引领这个目标数据对象
这就是void *最重要的一个地方
所以在我们C语言里边
它是通用的类型
什么东西都可以表示
因为如果它不能够表示
它一定能够表示那个东西的地址
你比如讲那个结构体 void *不能表示
但是我能够表示指向结构体的指针
能够表示那个结构体的地址啊
int *它也能够表示
实际上 因为在32位编译器下边
它这个void *指针
尺寸是固定的 4个字节
如果int刚好也是四个字节
int就直接可以转换成void *
反正尺寸是够的 你直接传进去就行了
它是通用的一个型
malloc这个函数怎么用呢
用法有一个特别的规矩
你就按照这个规矩来
假设我要存储一个字符串
我就定义一个指针类型的量char * p
然后我调用malloc为它分配内存
比如说我想分配一个
能够保存10个字符的一个字符串
那么我就应该这么写:malloc(11)
因为我要分配11个字节才够用
后面还有一个‘\0’呢 你必须加上
它返回那个指针类型是void *
所以我们要把它转换成char *
才能赋值给p这个指针量
malloc这个参数里面
就是你要分配的存储空间的大小
以字节为单位 这样的话
它这个分配好的指针
p所指向的那片存储区里面
就能够保存10个字符的一个字符串
后面会封装一个‘\0’的
我们看这样一个例子
让同学们写一个函数
来完成字符串赋值的一个动作
DuplicateString我要复制它
传的是一个char * s进来的
然后我要复制出去
形成一个副本 我要拷贝
这个地方指针是不能够直接赋值的
如果你指针直接赋值
那实际上就让两个指针
都指向目标字符串了
你就没有做出这个副本来
像复印机 我要复印
复印出来那个结果要有一个原件
还有一个复件
我本来有一个原件
还有一个复印件出来了
所以这就叫做一个副本
你如果是直接两个指针赋值过去
s赋值给t 那不叫副本
你是让两个指针指向同一个东西
那个t就表示“参见什么什么东西”
我们这个复制 拿到原始的串
然后根据那个原始字符串的大小
为它分配恰当的存储空间
然后一个字节、一个字节给我拷贝过去
包括最后‘\0’ 我们也给它构造进去
然后返回构造出来的那个串才可以
而这个串的构造
我们一定要动态地构造
因为在调用这个函数之前
我压根就不知道s所指向的那个串
有多少个字符
所以你没有办法预先假定
你新的串的存储空间要多大合适
你只能按照这个方式来
给我多长我就分配多长
所以我们要strlen 调用这个函数
来获取这个字符串的串长
获取这个字符串的串长之后
把这个串长这个值给n 然后malloc
分配n+1个字符的存储空间
然后把它返回的void *哑型指针
转换成char * 赋值给t
然后我写一个for循环
从s中给它拷贝到t里面去
在t的尾部封装一个‘\0’
return指针t就可以了
这个函数的实现非常重要
它给我们提供了一个很特殊一个机制
你看到我们所有的字符串
都要按照这样一个机制来进行实现
你要分配一个特定的存储区域
来存储目标串
而这个目标串的区域的大小
实际上和我们原始串
这个信息是相关的
我们要先得到这个原始串的串长信息
然后动态分配这个区域空间
构造它的目标串
也就是说 如果你的原始串是10个字符
那我就构造11个字节的存储空间
能够刚好存那个10个字符加‘\0’
如果你的原始串是20个字符
那么我们就为你分配21个字节
刚好存20个字符加上一个‘\0’
就是一个字符不多 一个字符不少
你不按照这个方式来
你就没有办法保证这一点
动态内存分配就能够做到这一点
这是非常有益的一个特性
相对应地 就是free这个函数
你刚才分配内存
当你不想用了 你就得free 就得要销毁它
只有动态内存才需要销毁
因为这个内存你是malloc出来的
你才需要free
如果不是malloc出来的 是不需要free的
你看我刚才那个例子
如果你真分配了这段内存
p作为指向字符一个指针
然后你分配了11个字节给它
后来你不用了
那么你就想销毁这个字符串
你就free(p)就可以了
注意销毁的不是p所代表这个指针量
销毁的是这个p所指向的
那个目标字符串那个存储空间
而不是p这个指针量本身的存储空间
p本身不需要被销毁
它要么是局部量 要么是全局量
第二个例子
你要想分配10个整数存储空间
那你就malloc(10*sizeof(int))
分配40个字节 转换成int *
指向整数的指针 初始化给p
然后free(p) 你就会销毁这40个字节
注意 p虽然是指向这个数组的0号元的
但是因为我们通过这个指针
实际上操纵的是40个字节的存储空间
所以一销毁就销毁这数组整体
而不是只是销毁这个数组的0号元
编译器不会干这个事情
一销毁就销毁这个数组的整体
所以同学们在使用这个
动态内存分配的时候
一定要记得有了malloc你就要有free
就是说你有了分配你就应该有释放
也叫有借有还 再借不难
整个计算机内存就那么多
你malloc一部分内存 后来你不用了
其实你就应该把它free给操作系统
然后以后它可以再次再利用
如果你总是malloc 你从来不free
如果你程序运行时间很长
你内存空间很容易就不够了
程序就没办法运行下去了嘛
程序做得不好 那可能就会崩溃掉
还有一点需要特别注意的就是什么呢
你free(p)以后 这个p作为一个指针量
它其实仍然指向你那串被你分配出来的
后来又被你销毁的那片存储区
p的值是存在的
但是那段内存区域已经无效了
所以在这种时候同学们要记得
free(p)之后最好紧跟着
完成一次p=NULL的指令
这样的话你写出来的程序才是最恰当的
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口