内存的申请和释放

C语言中的我们有时候需要手动申请内存空间,而手动申请的内存空间需要我们自己释放掉。这里我想总结一下变量与内存空间的关系。

变量

一般情况下,c程序中存在有全局变量,局部变量,静态变量,函数参数变量,字符串常量等类型的数据,这些数据存放的地址在内存中是不同的,有些变量在程序执行完退出时或者跳出该变量作用域时由系统释放响应的内存地址。但是有些变量占用的内存地址在跳出作用域后不能由操作系统自动释放,只能在程序退出时由操作系统决定是否释放,但是这就要看操作系统的心情了,有可能会出现没有释放的情况。如果跳出作用域后操作系统没有释放掉该变量占用的内存空间,那么就会造成内存泄漏的情况,如果程序一直运行的过程中过多得申请内存而得不到释放,那最严重的结果就是系统内存被分配完毕而导致程序异常退出。另外其他进程也会因申请不到内存而出现异常。

所以对内存空间的管理是用c或c++写程序的人需要清除并熟练掌握的,之前我没有很好得理解,项目中用到了很多相关知识,感觉学到很多,在这里总结一下。

变量存放的地方

首先程序中不同的变量类型是存放到不同的内存区域的,一个c或c++程序占用的内存大致分为以下几个部分:

  • 栈区: 用于存放程序中局部变量、函数变量,由操作系统释放内存空间。

  • 堆区: 用于存放由用户申请的内存空间,需要用户手动释放,否则只能等到程序结束后由操作系统释放。

  • 静态区(全局区) 存放全局变量和静态变量,这里还分初始化和未初始化两种类型,存放的区域都在静态区,但是分开存储。

  • 常量区 一般用于存放字符串常量,有操作系统释放。

  • 代码区 存储程序的二进制代码。

内存的释放

除了堆内存需要用户手动释放外,其他区域的内存地址都是不需要用户来释放的,所以这里主要讲一下用户自己申请了堆内存后自己手动释放的情况。

我们在创建局部变量(包括结构体变量)时,需要指定变量类型,比如是int、char或者int *、char *等,前两个是整型变量和字符变量,在创建时已经由系统分配了内存地址和空间,后两个是指针变量,根据其初始化或者后期赋值决定其指向的内存空间,但是没有分配内存空间,所以如果我们想要使用这两个变量,要么把他赋值给一个已经分配内存空间的地址,要么给他分配内存空间。比如:

1
2
3
int a = 1;
int *b; //b是一个地址,此时没有赋值,为: b = (nil)
b = &a; //此时b指向了a的地址,b的值就不再是null了,而是a所占内存空间的首地址:b = 0x7ffc3a2e93bc

另外数组也是在定义的时候就由系统分配了内存空间,内存首地址用数组名表示,所以数组名就是一个指针,指向数组大小的一块内存空间。所以数组的内存空间我们不需要手动释放。

当我们使用了malloc、relloc、calloc这三个函数从堆内存申请了一块内存空间时,这块内存空间在程序结束前时是不会由操作系统自动释放的,需要用户手动清理,清理的操作就是free函数,free的参数就是一个指向该内存区域的指针。比如:

1
2
3
4
5
char *name = NULL;
name = malloc(16); //为指针变量name申请内存空间,然后name就指向该内存的首地址
free(name); //释放name指向的内存空间。

char *file = "print.txt"; //指针直接指向字符串常量的内存地址,不需要手动释放,操作系统会自动释放,见常量区内容

另外程序中会经常用到结构体数据类型,其实跟正常变量类型一样,只不过结构体是把多种类型的变量组合到一起使用,方便记录同一类信息内容。

而链表中用到了结构体的内容,如果我们创建了一个链表,链表每个节点都是一个包含多种数据类型的结构体,结构体中还有指针变量指向用户自己分配的内存空间,当我们使用完这个链表后需要手动释放掉这个链表占用的内存空间,这时我们应该注意的是要释放的内存空间包括:

  • 每个链表节点申请的内存空间

  • 每个节点内指针变量指向的内存空间

总之要确保我们手动申请的内存空间都能正确被释放掉。内存空间释放掉后,指向这些空间的指针变量会随着其作用域消失或程序的退出而自动释放。

总结下来就是,自己的锅自己背,记住你申请了那些地址,然后不用的时候释放掉它们!

2018.05.30 北京 晴