C/C++学习之路_七: 内存管理


目录

  1. 作用域
  2. 内存布局
  3. 内存分区代码分析

1. 作用域

  1. C语言变量的作用域分为:

    1. 代码块作用域(代码块是{}之间的一段代码)
    2. 函数作用域
    3. 文件作用域

1. 局部变量

  1. 局部变量也叫auto自动变量(auto可写可不写),一般情况下代码块{}内部定义的变量都是自动变量,它有如下特点:

    1. 在一个函数内定义,只在函数范围内有效
    2. 在复合语句中定义,只在复合语句中有效
    3. 随着函数调用的结束或复合语句的结束局部变量的声明声明周期也结束
    4. 如果没有赋初值,内容为随机
void test() {//auto写不写是一样的,auto只能出现在{}内部auto int b = 10;
}int main(void) {//b = 100; //err, 在main作用域中没有bif (1) {//在复合语句中定义,只在复合语句中有效int a = 10;printf("a = %d\n", a);}//a = 10; //err离开if()的复合语句,a已经不存在return 0;
}

2. 静态局部变量

  1. static局部变量的作用域也是在定义的函数内有效
  2. static局部变量的生命周期和程序运行周期一样,执行main函数之前就已经开辟空间,程序结束之后才释放空间,同时staitc局部变量的值只初始化一次,但可以赋值多次
  3. static局部变量若未赋以初值,则由系统自动赋值:数值型变量自动赋初值0,字符型变量赋空字符
void fun1() {int i = 0;i++;printf("i = %d\n", i);
}void fun2() {//静态局部变量,没有赋值,系统赋值为0,而且只会初始化一次static int a;a++;printf("a = %d\n", a);
}int main(void) {fun1();fun1();fun2();fun2();return 0;
}

3. 全局变量

  1. 在函数外定义,可被本文件及其它文件中的函数所共用,若其它文件中的函数调用此变量,须用extern声明
  2. 全局变量的生命周期和程序运行周期一样,执行main函数之前就已经开辟空间,程序结束之后才释放空间
  3. 不同文件的全局变量不可重名

4. 静态全局变量

  1. 在函数外定义,作用范围被限制在所定义的文件中
  2. 不同文件静态全局变量可以重名,但作用域不冲突
  3. static全局变量的生命周期和程序运行周期一样,同时staitc全局变量的值只初始化一次,执行main函数之前就已经开辟空间,程序结束之后才释放空间

5. extern全局变量声明

  1. extern int a; //声明一个变量,这个全局变量在别的文件中已经定义了,这里只是声明,而不是定义。

6. 全局函数和静态函数

  1. 在C语言中函数默认都是全局的,使用关键字static可以将函数声明为静态,函数定义为static就意味着这个函数只能在定义这个函数的文件中使用,在其他文件中不能调用,即使在其他文件中声明这个函数都没用。
  2. 对于不同文件中的staitc函数名字可以相同。
  3. 注意:
    1. 允许在不同的函数中使用相同的变量名,它们代表不同的对象,分配不同的单元,互不干扰。
    2. 同一源文件中,允许全局变量和局部变量同名,在局部变量的作用域内,全局变量不起作用。
    3. 所有的函数默认都是全局的,意味着所有的函数都不能重名,但如果是staitc函数,那么作用域是文件级的,所以不同的文件static函数名是可以相同的。

7. 总结

类型 作用域 生命周期
auto变量 一对{}内 当前函数
static局部变量 一对{}内 整个程序运行期
extern变量 整个程序 整个程序运行期
static全局变量 当前文件 整个程序运行期
extern函数 整个程序 整个程序运行期
static函数 当前文件 整个程序运行期
register变量 一对{}内 当前函数
全局变量 整个程序 整个程序运行期

2. 内存布局

1. 内存分区

  1. 程序没有加载到内存前,可执行程序内部已经分好3段信息,分别为代码区(text)、数据区(data)和未初始化数据区(bss)3 个部分,有些人直接把data和bss合起来叫做静态区或全局区。

1. 代码区

  1. 文本段也称代码段,进程启动时会将程序的代码加载到物理内存中,文本段映射到这片物理内存,这片内存是不可以在运行期间修改的。
  2. 通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
  3. 代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。
  4. 另外,代码区还规划了局部变量的相关信息。

2. 已初始化数据段(data段)

  1. 已初始化数据段包含了在程序中明确被初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
  2. 在程序真正运行前就已经确定的数据,所以可以提前加载到内存保存好,生存周期为整个程序运行过程。
  3. 需注意的是,数据段不是只读的,在运行时变量值是可以变动的。
  4. 数据段还可以更细的分为初始化只读区以及初始化可读写区。举例:
    1. 全局字符串 char s[] = “hello world”,全局变量 int debug=1,静态变量 static int i = 10 存储在初始化可读写区;
    2. 另一种情况下,const char* string = “hello world”,字符串“hello world”存储在初始化只读区,string指针则存在初始化可读写区。

3. 未初始化数据区(又叫 bss 区)

  1. 未初始化数据区BSS存储未初始化的全局变量和静态变量,这些变量的值是在程序真正运行起来并赋值后才能确定的,所以程序加载的时候,只需要记录它的内存地址和所需大小。。
  2. 程序在加载到内存前,代码区和全局区(data和bss)的大小就是固定的,程序运行期间不能改变。
  3. 然后,运行可执行程序,系统把程序加载到内存,除了根据可执行程序的信息分出代码区(text)、数据区(data)和未初始化数据区(bss)之外,还额外增加了栈区、堆区。
  4. 举例:变量 static int i; 全局变量 int j; 包含在BBS段中

4. 堆(heap)

  1. 堆用于运行时内存分配,在内存中位于BSS区和栈区之间。
  2. 堆用于存储那些生存期与函数调用无关的数据。比如系统调用 malloc 申请的内存就分配在堆上,这些申请的内存在不需要时需要手动释放,否则会出现内存泄漏。堆在程序结束时由操作系统回收。
  3. 堆中内容是匿名的,只能通过指针间接访问。
  4. 堆空间被进程内所有的共享库及动态加载模块所共享。

5. 栈(stack)

栈是一个由内核动态调整的内存段落,由栈帧(Stack Frames)组成,进程每调用一个函数,都将为该函数分配一个栈帧,栈帧中保存了该函数的局部变量、参数值和返回值,栈帧会在函数返回时清理掉。
在程序运行过程中实时的加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。
4. 栈存储着自动变量以及每次函数调用时保存的信息,每当函数被调用时,返回地址以及调用者的上下文环境例如一些机器寄存器都存储在栈中,新的被调用函数此时会在栈上重新分配自动或者临时变量,这就是递归函数的工作原理。
5. 每当函数递归调用自己时,都会使用新的栈帧,因此当前函数实体内的栈帧内的变量不会影响另外一个函数实体内的变量。
栈主要有三个用途:
为函数内部声明的非静态局部变量(局部变量)提供存储空间。
记录函数调用过程相关的维护性信息,称为栈帧或过程活动记录
临时存储区,用于临时存放长算术表达式部分计算结果或alloca()函数分配的栈内内存。

2. 存储类型总结

类型 作用域 生命周期 存储位置
auto变量 一对{}内 当前函数 栈区
static局部变量 一对{}内 整个程序运行期 初始化在data段,未初始化在BSS段
extern变量 整个程序 整个程序运行期 初始化在data段,未初始化在BSS段
static全局变量 当前文件 整个程序运行期 初始化在data段,未初始化在BSS段
extern函数 整个程序 整个程序运行期 代码区
static函数 当前文件 整个程序运行期 代码区
register变量 一对{}内 当前函数 运行时存储在CPU寄存器
字符串常量 当前文件 整个程序运行期 data段
int e;
static int f;
int g = 10;
static int h = 10;int main() {int a;int b = 10;static int c;static int d = 10;char *i = "test";char *k = NULL;printf("&a\t %p\t //局部未初始化变量\n", &a);printf("&b\t %p\t //局部初始化变量\n", &b);printf("&c\t %p\t //静态局部未初始化变量\n", &c);printf("&d\t %p\t //静态局部初始化变量\n", &d);printf("&e\t %p\t //全局未初始化变量\n", &e);printf("&f\t %p\t //全局静态未初始化变量\n", &f);printf("&g\t %p\t //全局初始化变量\n", &g);printf("&h\t %p\t //全局静态初始化变量\n", &h);printf("i\t %p\t //只读数据(文字常量区)\n", i);k = (char *) malloc(10);printf("k\t %p\t //动态分配的内存\n", k);return 0;
}

3. 内存操作函数

1. memset()

#include <string.h>
void *memset(void *s, int c, size_t n);
功能:将s的内存区域的前n个字节以参数c填入
参数:s:需要操作内存s的首地址c:填充的字符,c虽然参数为int,但必须是unsigned char , 范围为0~255n:指定需要设置的大小
返回值:s的首地址int a[10];memset(a, 0, sizeof(a));memset(a, 97, sizeof(a));int i = 0;for (i = 0; i < 10; i++){printf("%c\n", a[i]);}

2. memcpy()

#include <string.h>
void *memcpy(void *dest, const void *src, size_t n);
功能:拷贝src所指的内存内容的前n个字节到dest所指的内存地址上。
参数:dest:目的内存首地址src:源内存首地址,注意:dest和src所指的内存空间不可重叠,可能会导致程序报错n:需要拷贝的字节数
返回值:dest的首地址int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int b[10];memcpy(b, a, sizeof(a));int i = 0;for (i = 0; i < 10; i++){printf("%d, ", b[i]);}printf("\n");//memcpy(&a[3], a, 5 * sizeof(int)); //err, 内存重叠

3. memmove()

  1. memmove()功能用法和memcpy()一样,区别在于:dest和src所指的内存空间重叠时,memmove()仍然能处理,不过执行效率比memcpy()低些。

4. memcmp()

#include <string.h>
int memcmp(const void *s1, const void *s2, size_t n);
功能:比较s1和s2所指向内存区域的前n个字节
参数:s1:内存首地址1s2:内存首地址2n:需比较的前n个字节
返回值:相等:=0大于:>0小于:<0int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int b[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int flag = memcmp(a, b, sizeof(a));printf("flag = %d\n", flag);

4. 堆区内存分配和释放

1. malloc()

#include <stdlib.h>
void *malloc(size_t size);
功能:在内存的动态存储区(堆区)中分配一块长度为size字节的连续区域,用来存放类型说明符指定的类型。分配的内存空间内容不确定,一般使用memset初始化。
参数:size:需要分配内存大小(单位:字节)
返回值:
成功:分配空间的起始地址
失败:NULL#include <stdlib.h>
#include <stdio.h>
#include <string.h>int main(){int count, *array, n;printf("请输入要申请数组的个数:\n");scanf("%d", &n);array = (int *) malloc(n * sizeof(int));if (array == NULL) {printf("申请空间失败!\n");return -1;}//将申请到空间清0memset(array, 0, sizeof(int) * n);for (count = 0; count < n; count++) /*给数组赋值*/array[count] = count;for (count = 0; count < n; count++) /*打印数组元素*/printf("%2d", array[count]);free(array);return 0;
}

2. free()

#include <stdlib.h>
void free(void *ptr);
功能:释放ptr所指向的一块内存空间,ptr是一个任意类型的指针变量,指向被释放区域的首地址。对同一内存空间多次释放会出错。
参数:
ptr:需要释放空间的首地址,被释放区应是由malloc函数所分配的区域。
返回值:无

3. 内存分区代码分析

1. 返回栈区地址

#include <stdio.h>
int *fun() {int a = 10;return &a;//函数调用完毕,a释放
}int main(int argc, char *argv[]) {int *p = NULL;p = fun();*p = 100; //操作野指针指向的内存,errreturn 0;
}

2. 返回data区地址

#include <stdio.h>int *fun() {static int a = 10;return &a; //函数调用完毕,a不释放
}int main(int argc, char *argv[]) {int *p = NULL;p = fun();*p = 100; //okprintf("*p = %d\n", *p);return 0;
}

3. 值传递1

#include <stdio.h>
#include <stdlib.h>void fun(int *tmp) {tmp = (int *) malloc(sizeof(int));*tmp = 100;
}int main(int argc, char *argv[]) {int *p = NULL;fun(p); //值传递,形参修改不会影响实参printf("*p = %d\n", *p);//err,操作空指针指向的内存return 0;
}

4. 值传递2

#include <stdio.h>
#include <stdlib.h>void fun(int *tmp) {*tmp = 100;
}int main(int argc, char *argv[]) {int *p = NULL;p = (int *) malloc(sizeof(int));fun(p); //值传递printf("*p = %d\n", *p); //ok,*p为100return 0;
}

5. 返回堆区地址

#include <stdio.h>
#include <stdlib.h>int *fun() {int *tmp = NULL;tmp = (int *) malloc(sizeof(int));*tmp = 100;return tmp;//返回堆区地址,函数调用完毕,不释放
}int main(int argc, char *argv[]) {int *p = NULL;p = fun();printf("*p = %d\n", *p);//ok//堆区空间,使用完毕,手动释放if (p != NULL) {free(p);p = NULL;}return 0;
}

C/C++学习之路_七: 内存管理相关推荐

  1. C/C++学习之路_九:文件操作

    C/C++学习之路_九:文件操作 目录 概述 文件的顺序读写 文件的随机读写 windows和linux文本 获取文件状态 删除文件.重命名文件 文件缓冲区 1. 概述 1. 磁盘文件和设备文件 磁盘 ...

  2. C/C++学习之路_八: 复合类型

    C/C++学习之路_八: 复合类型 目录 结构体 共用体(联合体) 枚举 typedef 1. 结构体 1. 概述 有时我们需要将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年 ...

  3. C/C++学习之路_六: 指针

    C/C++学习之路_第六章: 指针 目录 概述 指针基础知识 指针和数组 多级指针 指针和函数 指针和字符串 指针小结 1. 概述 1. 内存 内存含义: 存储器:计算机的组成中,用来存储程序和数据, ...

  4. alin的学习之路:共享内存

    alin的学习之路:共享内存 1. 概念 共享内存是进程间通信中效率最高的一种方式. 共享内存: 可以被多个进程同时使用的一块内核的内存 有血缘关系的进程 没有血缘关系的进程 这块内存不属于任何的进程 ...

  5. [EntLib]微软企业库5.0 学习之路——第七步、Cryptographer加密模块简单分析、自定义加密接口及使用—上篇...

    在完成了后,今天开始介绍企业库中的新模块:Cryptographer(加密模块),这个模块在日常的大多数项目的作用非常重要,例如:网站会员密码.身份证号.网站配置等,通过对信息进行加密可以保证项目数据 ...

  6. 剩余 大小 查看内存_计算机内存管理介绍

    作者:Adam原文:https://www.cnblogs.com/adamwong/p/10678015.html 计算机操作系统内存管理是十分重要的,因为其中涉及到很多设计很多算法.<深入理 ...

  7. 【JVM高级特性与最佳实践(第3版)-周志明】-学习记录之【自动内存管理】

    写在前面,不是原创,是周志明老师书里得内容,方便个人随时学习查看,因为发布得时候没有这个选项,只能选原创 一.概述 对于Java程序员来说,在虚拟机自动内存管理机制的帮助下,不再需要为每一个new操作 ...

  8. mysql内存管理,学习猿地-闲谈 MySQL内存管理,内存分配器和操作系统

    当用户使用任何软件(包括MySQL)碰到内存问题时,我们第一反应就是内存泄漏.正如这篇文章所示,其实并不总是这样. 这篇文章阐述一个关于内存的bug. All Percona Support cust ...

  9. Objective-C基础教程学习笔记(九)内存管理

    每个对象都维护一个保留计数器.对象被创建时,其保留计数器值1:对象被保留时,保留计数器值加1:对象被释放时,保留计数器值减1:当保留计数器值归0时,对象被销毁.在销毁对象时,首先调用对象的deallo ...

最新文章

  1. oracle+rac+算法,Oracle RAC中的投票算法
  2. 菜鸟驿站是什么快递_95后女生在横店开菜鸟驿站,每天帮大明星和小龙套收快递...
  3. OCP12C题库,62数据库备份与恢复(admin,install and upgrade accelerated, backup and recovery workshop -62)(新增)
  4. 设计模式(2)--Singleton(单例模式)--创建型
  5. linux tcp server开源,GitHub - 06linux/cellnet: 高性能,简单,方便的开源服务器网络库...
  6. Perl 第二章 簡單變量
  7. pytorch以特征图的输入方式训练LSTM模型
  8. javacript 字典
  9. 深度学习——Optimizer算法学习笔记
  10. js获取url传递得参数
  11. Linux命令对应的英文全称
  12. stuxnet震网病毒科普
  13. 采用计算机教学的优点,小学计算机教学的特点与重点
  14. i7台式电脑配置推荐_高配游戏电脑 intel酷睿i7-8700配RTX2070六核台式电脑配置清单表...
  15. excel 数组公式
  16. flowable 排他网关
  17. SpringBoot项目入门,前端thymeleaf,后端Java,数据库Jpa+MySQL
  18. 《中国管理信息化》期刊简介及投稿要求
  19. 股票代码与上市公司板块分类
  20. 【Opencv】【C++】 Opencv之calcHist() 计算直方图

热门文章

  1. Puppet exec资源介绍(二十六)
  2. 腾讯微博发表带图片的微博
  3. MySQL空密码用户清理
  4. ExcelJS —— Node 的 Excel 读写扩展模块2
  5. 《UCD火花集2:有效的互联网产品设计 交互/信息设计 用户研究讨论》一2.3 交互设计师容易犯的错误:把自己禁锢在解决方案之中...
  6. oracle学习篇一:sqlplus常用命令
  7. CodeForces - 628D Magic Numbers(数位dp)
  8. 牛客 - 二分(差分)
  9. 图文解释Glados自动签到免费获取天数(腾讯云函数serverless版)
  10. 【数据结构】哈夫曼树与哈夫曼编码