C语言之内存模型以及动态内存分配
目录
内存分区模型
C/C++内存开辟
按照程序运行前后分区
程序运行前
代码区
全局区
程序运行后
栈区
堆区
关于栈
栈与静态区(数据段)
动态内存分配
那么使用动态内存的好处在哪?
常见的内存使用方式
动态内存分配函数
malloc函数
free函数
calloc函数
realloc函数
常见动态内存错误
柔性数组
内存分区模型
C/C++程序在运行时,将内存大方向划分为4个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,那么程序结束时由操作系统回收。
内存分区的意义:不同区域存放的数据被赋予不同的生命周期,给我们更大的灵活编程
C/C++内存开辟
按照程序运行前后分区
程序运行前
前言:程序编译后,生成exe可执行程序,未执行该程序前分为2个区域
代码区
- 代码区里面存放CPU执行的机器指令
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外的修改他的指令
全局区
- 全局变量和静态变量存放在此
- 全局区还包含了常量区,字符串常量和其他常量(非const修饰的局部常量)也存放在此
- 该区域的数据在程序结束后由操作系统释放
程序运行后
前言:在程序执行后又划分为2个区域
栈区
- 由编译器自动分配释放,存放函数的参数值,局部变量等
- 注意返回值不要返回局部变量的地址,因为栈区开辟的数据会由编译器自动释放
具体案例
#include <iostream>
using namespace std;
int* func() {int a = 10;return &a;
}
void main() {int* p = func();//编译器给我们做了一次保留cout << "a的值为:" << *p << endl;//再次打印失效cout << "a的值为:" << *p << endl;
}
堆区
- 由程序员分配和释放,若程序员不释放则程序结束时由操作系统回收
- 在C++中主要利用new在堆区开辟空间,在C语言中主要用动态内存分配函数
关于栈
栈:一种数据结构,遵循先进后出原则,类似弹夹
理解:
- 往栈里放数据就类似于往弹夹里装子弹称为压栈也称入栈
- 从栈里取数据就类似于从弹夹里取子弹称为弹栈也称出栈
栈区的使用习惯:先用高地址,再用低地址
注意:每一个函数调用都需要在栈区上给自己分配一个空间(栈内遵循先进后出原则)
绝大多数编译器传参顺序从右往左,并且传参的过程就是压栈的过程
整个过程
在栈内开辟main函数的空间——main函数对空间进行管理分别为局部变量分配空间——调用函数时从右往左进行实参的压栈并对Add函数分配栈内空间——Add函数对空间进行管理分别为局部变量分配空间——a'+b'赋值给z——将z赋值给c——销毁Add函数空间以及a',b'空间(弹栈)。
栈与静态区(数据段)
实际上普通的局部变量是在栈区分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁但是被static修饰的变量存放在数据段(静态区),数据段的特点是在上面创建的变量,直到程序结束才销毁所以生命周期变长。
动态内存分配
动态内存分配,顾名思义就是可以使我们更加灵活的分配和运用内存,动态内存的开辟的释放都是按照我们自身的需要进行的,我们通常直接使用的内存是在栈上的,开辟和释放内存都是由系统自动进行的,而动态内存分配的内存是在堆上。
那么使用动态内存的好处在哪?
栈上的空间是自动分配自动回收的,因此栈上的数据的生存周期只是在函数的运行过程当中,运行后就释放掉,不能够再访问。在堆上开辟的内存,不会被系统自动释放,只能通过我们自己释放或者当程序结束的时候被自动回收,只要我们有指向由我们申请在堆上的空间的指针,我们就能对它进行修改数据和使用。
常见的内存使用方式
1.创建一个变量
//(变量)局部变量放在栈区,全局变量放在静态区
int a = 10;
//(数组)局部变量放在栈区,全局变量放在静态区
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
上述开辟空间有2个特点
- 空间开辟大小是固定的
- 数组在声明的时候,必须指定数组的长度,他所需要的内存在编译时分配
但对于空间的需求不仅仅是上述情况,有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的方式便不再满足,这时候就需要试试动态内存分配了
为什么有动态内存分配
原因:当前我们这两种(创建变量和创建数组的方式)不能满足我们来创建一个我们想要创建多大空间就能创造多大空间的需求
动态内存分配函数
malloc函数
这个函数向内存中申请一块连续可用的size字节的空间(值为随机,没进行初始化),并返回指向这个空间的指针
free函数
该函数用来做动态内存的释放和回收,传入的ptr为申请的动态内存空间的地址
对上面两个函数的使用:
引入头文件:#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
void main() {//向堆内存中申请10个整形的空间int* p=(int*)malloc(10 * sizeof(int));if (p == NULL) {//打印错误原因的一个方式perror("malloc");}else{//正常使用空间int i = 0;for (i = 0; i < 10; i++){*(p + i) = i;//对内存进行修改printf("%d\n", *(p + i));//0,1,2,3,4,5,6,7,8,9}}//当动态内存空间不使用后,就应该还给操作系统free(p);//空间虽已经释放,但还可以通过p找到该地址p = NULL;
}
注意:如果没有free函数,那么执行到main函数的最末尾,malloc所申请的空间依然存在,直到main函数结束(不是普通的函数)——程序结束(生命周期到了),其也会把malloc函数申请的这块内存还给操作系统。
calloc函数
在内存中开辟一个数组,并且数组的每个元素初始化为0,里面的num为元素的个数,size为每个元素的大小,之后会返回该数组所在的地址,如果空间不够用,同样会返回null
使用:
引入头文件:#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
void main() {int* p = (int*)calloc(10, sizeof(int));if (p == NULL) {printf("%s\n", strerror(errno));}else {int i = 0;for (i = 0; i < 10; i++) {printf("%d\n",*(p + 1));//0,0,0,0,0,0,0,0,0,0(初始化全为0)}}//释放空间free(p);//用来释放动态开辟的空间p = NULL;
}
realloc函数
调整动态开辟内存的大小
- realloc函数的出现让动态内存管理更加灵活
- 有时我们发现过去申请的空间太小了,有时候我们又觉得申请的空间太大了,那为了合理的控制内存,我们一定会对内存的大小做灵活的调整,realloc函数就可以做到对动态开辟内存大小的调整。
- ptr是要调整的内存地址
- size为调整之后的新大小
- 返回值为调整之后内存的起始位置
- 这个函数调整原内存空间大小的基础上,还会将原内存的数据移动到新空间
realloc函数使用注意事项
- 如果p(开始内存空间的地址)指向的的空间之后有足够的内存空间可以追加,那么realloc函数会直接追加并返回p
- 如果p(开始内存空间的地址)指向的的空间之后没有足够的内存空间可以追加那么realloc函数会重新找一个新的内存区域,开辟一块满足需求的空间,并把原来内存中的数据拷贝回来,释放旧的内存空间,最后返回新开辟内存空间的地址
使用:
引入头文件:#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
void main() {int* p = (int*)malloc(20);if (p == NULL) {perror("malloc");}else {int i = 0;for (i = 0; i <5; i++) {*(p + i) = i;printf("%d ", *(p + i));//0 1 2 3 4}printf("\n");}//假设到这里20字节不能满足使用要求了,欲再填20字节//为了避免realloc函数调用失败把p地址搞丢执行下3行int* ptr=(int*)realloc(p, 40);if (ptr != NULL) {p = ptr;}int i = 0;for ( i = 0; i < 10; i++){printf("%d ", *(p + i));//0 1 2 3 4 -842150451 -842150451 -842150451 -842150451 -842150451(随机值)}//释放内存空间free(p);p = NULL;
}
常见动态内存错误
- 对空指针解引用操作
- 对动态开辟内存的越界访问
- 对非动态空间free
- 使用free释放动态开辟内存的一部分
- 对同一块动态内存多次释放
- 对动态开辟的内存空间忘记释放
#include <stdio.h>
#include <stdlib.h>
void main() {int* p = (int*)malloc(20);if (p == NULL) {perror("malloc");}else {int i = 0;for (i = 0; i <5; i++) {*p++ = i;}}//回收空间,此时p已经不是首地址了,释放空间报错free(p);p = NULL;
}
柔性数组
定义:C99中,结构体中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员
柔性数组特点
举例:
#include <stdio.h>
#include <stdlib.h>
struct Stu
{int n;int arr[];//未知大小的-柔性数组成员-数组的大小是可以调整的(也可以这样写——int arr[0])
};
void main() {struct Stu s;printf("%d\n", sizeof(s));//4//由此观之,在计算含有柔性数组的结构体的大小时,计算的大小不包含柔性数组的大小//为结构体Stu开辟空间,并且保证柔性数组有5个int类型变量的空间struct Stu* ps = (struct Stu*)malloc(sizeof(struct Stu) + 5 * sizeof(int));ps->n = 100;for (int i = 0; i < 5; i++){ps->arr[i] = i;//0,1,2,3,4printf("%d ", ps->arr[i]);}printf("\n");//如今对于数组来说5个整形不够了,要再添5个struct Stu* ptr = (struct Stu*)realloc(ps, 44);if (ptr!=NULL){ps = ptr;}else{perror("realloc");}for (int i = 5; i < 10; i++){ps->arr[i] = i;printf("%d ", ps->arr[i]);//5,6,7,8,9}printf("\n");free(ps);ps = NULL;
}
替代方式
#include <stdio.h>
#include <stdlib.h>
struct Stu
{int n;int* arr;
};
void main() {struct Stu* ps = (struct Stu*)malloc(sizeof(struct Stu));ps->arr = malloc(5 * sizeof(int));for (int i = 0; i < 5; i++){ps->arr[i] = i;printf("%d ", ps->arr[i]);}printf("\n");//再加20个字节空间int* ptr = (int*)realloc(ps->arr, 10 * sizeof(int));if (ptr!=NULL){ps->arr = ptr;}else {perror("realloc");}for (int i = 5; i < 10; i++){ps->arr[i] = i;printf("%d ", ps->arr[i]);}printf("\n");free(ps->arr);//先释放ps->arrps->arr = NULL;free(ps);ps = NULL;
}
柔性数组的优点
- 方便内存释放——只需要一次的内存释放
- 有利于访问速度——连续内存有益于提高访问速度,也有利于减少内存碎片(一次分配内存即可)
C语言之内存模型以及动态内存分配相关推荐
- 【学习笔记】第三章——内存 I(交换技术、进程七状态模型、动态分区分配、动态分区算法)
文章目录 一. 交换状态.进程七状态模型 二. 内存的分配方式 三. 动态分配算法 一. 交换状态.进程七状态模型 交换技术:内存空间紧张,把某些进程暂时换出外存.(进程在内存与磁盘间动态调度) 进程 ...
- JVM内存模型 和 Java内存模型 对比学习
前言 首先要知道这两者不是同一个东西,Jvm内存模型 也叫 Java内存区域.Java运行时数据区域 而Java内存模型 是 JMM (Java Memory Model,简称 JMM),是定义了线程 ...
- jvm内存模型_JVM|02内存模型
JVM内存模型 概述 Java内存模型(Java Memory Model ,JMM)就是一种符合内存模型规范的,屏蔽了各种硬件和操作系统的访问差异的,保证了Java程序在各种平台下对内存的访问都能保 ...
- java ps old gen perm gen_Java 内存模型之堆内存管理
Java 内存模型之堆内存(Heap) 一.背景知识:内存类型介绍 1.什么是 Perm Gen? Perm Gen : Permanent Generation Perm Gen 区是一个特殊的JV ...
- 栈内存 ,堆内存区别 C++ 动态内存 == 与equal区别 复合函数奇偶性 三角函数转换公式: 虚函数和纯虚函数: C++ 中的运算符重载 数据封装,数据抽象 C++ 接口(抽象类
目录 栈内存 ,堆内存区别 C++ 动态内存 == 与equal区别 复合函数奇偶性 三角函数转换公式: 虚函数和纯虚函数: C++ 中的运算符重载 数据封装,数据抽象 C++ 接口(抽象类): #和 ...
- 深入理解并发内存模型||JMM与内存屏障||多核并发缓存架构 ||JMM内存模型||volatile 关键字的作用 ||JMM 数据原子操作||JMM缓存不一致的问题
深入理解并发内存模型||JMM与内存屏障 多核并发缓存架构 JMM内存模型 volatile 关键字的作用 JMM 数据原子操作 JMM缓存不一致的问题
- JUC多线程:JMM内存模型与volatile内存语义
一.JMM 内存模型: 1.什么是 JMM 内存模型: Java 内存模型是 Java 虚拟机定义的一种多线程访问 Java 内存各个变量的访问规范,主要围绕如何解决并发过程中的原子性.可见性.有序性 ...
- C语言之动态内存管理与动态内存函数
文章目录 一.为什么存在动态内存分配? 二.动态内存函数的介绍 1.malloc和free 2.calloc函数 3.realloc函数 一.为什么存在动态内存分配? 学习动态内存的管理方法之前,我们 ...
- C语言程序设计 | 动态内存管理:动态内存函数介绍,常见的动态内存错误,柔性数组
动态内存管理目录: 动态内存函数的介绍 常见的动态内存函数的错误 柔性数组 为什么会有动态内存管理呢 我们在日常使用中,创建一个数组,一个变量时都会开辟空间 如: int a; //在栈上开辟一个四字 ...
最新文章
- NSHomeDirectory
- JDBC驱动的动态加载
- java 取日期_java取的日期的操作
- Oozie基于Hue全流程调度
- leetcode刷题之树(1)
- 用友U8清除站点异常任务SQL语句
- Django笔记 —— 表单(form)
- 使用PyTorch建立您的第一个文本分类模型
- cocos2dx 显示html,cocos2dx - 在游戏中显示HTML页面
- oracle应付创建会计科目,求助: EBSR12应付发票取消后创建不成会计科目
- Android开发——数据库框架Suger遇到的大坑(依据列名查询不到数据解决办法)
- C语言基础之统计数字
- 【图像去噪】基于自适应小波阙值算法实现图像去噪附matlab代码
- 【转载】R6034错误,C Runtime Error
- 安云网络-高防服务器租用的稳定性
- sapvl10a增强_教你搞定SAP屏幕增强
- firefox浏览器和火狐浏览器的驱动匹配问题,及对应的版本驱动下载(能解决99%问题)收藏
- 读IC封装基础与工程设计实例记录一
- u盘变成RAW格式怎么修复?好用的教程分享
- android仿微信联系人索引列表