c语言内存管理2

课程纲要

目录

一. C语言内存管理模型 1

1.c/C++定义了4个内存区间: 1

2.栈区和堆区优缺点: 1

3.堆内存的分配与释放(malloc/free) 2

4.变量的存储类型 2

二.内存管理应用举例 3

 

 

 

一.C语言内存管理模型

1.c/C++定义了4个内存区间

代码区  静态变量区  栈区 堆区(动态存储区)

 

  1. 代码区:存放CPU执行的机器指令,代码区是可共享,并且是只读的(指令,常量字符串)
  2. 静态区:所有的 全局变量+静态变量(static)+字符串常量
  3. 栈区:由编译器自动分配内存(函数的参数,局部变量,自动变量),自动回收。
  4. 堆区:由程序员分配释放(malloc, free)

栈区生命周期:函数调用结束,内存空间就会释放

静态区生命周期:整个程序结束内存才会释放。注意static修饰词的使用

 

 

2.栈区和堆区优缺点:

栈区:

优点:自动申请、自动释放,使用方便,并且能与标识符建立联系使用方便,由于操作系统算法比较完善,因此不会产生内存碎片、内存泄漏的问题。先进后出的特殊为函数的调用提供以及递归支持。安全、方便。

缺点:大小有限,不适合存储大量数据;当函数结束时栈内存就会被释放, 不适合长期存储数据。

 

堆区:

优点:存储空间够大,适合存储大批量的数据,申请和释放是受管理员控制的,适合长期保存数据。还可以根据程序的实际需要来调整内存的大小。

缺点:需要手动申请释放,使用麻烦;安全性差;在使用过程中可能会造成内存泄漏、产生内存碎片,对编程人员要求高。

 

 

3.堆内存的分配与释放(malloc/free)

当程序运行到需要一个动态分配的变量或对象时,必须向系统申请取得堆中的一块所需大小的存贮空间,用于存贮该变量或对象。当不再使用该变量或对象时,也就是它的生命结束时,要显式释放它所占用的存贮空间,这样系统就能对该堆空间进行再次分配,做到重复使用有限的资源。

堆区是不会自动在分配时做初始化的(包括清零),所以必须用初始化式来显式初始化。

 

使用堆内存要注意哪些问题:

1、内存越界

由于申请时的内存大小计算错误,而导致内存越界。

会导致段错误、脏数据(数据丢失受破坏)

2、内存泄漏

在程序运行期间,内存的首地址丢失,造成内存无法再内存期间释放,或者重复申请,而导致可用内存越来越少。

3、内存碎片

如果频繁的申请、释放小块的内存可能会造成申请和释放的不协调,而导致已经释放的内存无法使用。

4、重复释放

指针操作失误、业务流程出现漏洞导致同一块内存多次释放。可能造成段错误。

5、产生野指针

释放完内存后指向他的指针要及时设置为空,否则就会产生野指针,埋下安全隐患。

 

思考:

我们会想,既然有全局变量区(静态变量区),为什么还要使用堆区呢?直接一个全局变量不就可以了?

 

实际在使用的时候会发现,堆区其实是很有必要的。比如说,一个板卡串口每隔一定周期会收到一个1k字节的数据,拿到1k的数据后需要通过蓝牙上传给手机,蓝牙一次只传20个,还不能一直使用for不断调用蓝牙发送函数,需要用定时器辅助完成发送。这个时候这个1k的buff就不能是局部,因为不能一直停在uart接受函数这里,需要用到定时器持续蓝牙。如果使用局部的buff,传输uart接受函数执行完毕后这个1k的buff就释放了,定时器来到的时候数据已经没了。如果使用全局,确实不会出现上述的问题,但是你这1k的ram就只能一直做接受uart数据这一件事,那么malloc的神奇就来,我在收uart的时候申请一下1k的buff,蓝牙传输完成以后就可以free掉,这样其他程序可以反复用到这1k的ram空间;

尤其是我们的单片机内存现在还不是很大,随着物联网应用的发展一些包和逻辑占用较多内存。我们可以使用动态分配的这种方法使有限的内存实现更多的功能。

 

4.变量的存储类型

变量的定义(存储类型 数据类型 变量名)

Auto int aa=10;//我们平时定义都将auto省略

 

(1)Auto (局部变量)说明变量只能在某个程序范围内使用,通常在函数体内或函数中的复合语句中。(默认是随机值,所以我们平时都要求变量要赋初值)

 

(2)Register 寄存器变量(不常用),想将变量放入CPU寄存器中,这样可以加快程序运行速度。

如果申请不到还是使用一般内存,同auto。

 

(3)Static 变量 静态存储类型的变量,即可以在函数体内,也可以在函数体外(可以修饰局部变量,也可以修饰全局变量,默认值为0)

 

 

二.内存管理应用举例

1.

c语言内存管理2

这是stm32f103RE的Memory map,从这个图可以看出外设的地址,64K的RAM空间,及512K的FLASH。

在平时的开发中,我们要时刻关注这64K,RAM的剩余量,防止程序跑飞。那么我们编译的程序怎么看内存占用,最简单的方式就是直接看编译信息。

 

程序通过软件进行编译输出如下信息

Program Size: Code=10904  RO-data=940  RW-data=40  ZI-data=1736

 

Code是代码占用的空间;

RO-data是 Read Only 只读常量的大小,如const型;

RW-data是(Read Write) 初始化了的可读写变量的大小;

ZI-data是(Zero Initialize) 没有初始化的可读写变量的大小。ZI-data不会被算做代码里因为不会被初始化;

 

烧写的时候是FLASH中的被占用的空间为:Code + RO Data + RW Data

程序运行的时候,芯片内部RAM使用的空间为: RW Data + ZI Data

比如我们经常遇到的内存溢出错误就是RW Data + ZI Data 大于了我们芯片的内存空间。

 

 

 

2.内存分配举例:

c语言内存管理2

 

 

3.如下面的程序:

char * get_string(){

char s[] = “hello world”;

return s;

}

void main(void)

{

char *str =NULL;

str = get_string();

}

如上面的程序所示,main函数中执行完get_string时,返回的内容是否是hello world??

答案是返回的内容不是hello world。

 

我们的“hello world”是一个字符串常量,它位于静态存储区,但是我们把字符串常量赋值给了一个局部变量(char型的数组),局部变量是存放在栈上的,当get_sting函数退出时,局部变量的内存是要被清空的。

那我们改为char *s = “hello world”;这样就可以正常输出了,s是指向静态存储区的内存,静态存储区的生命周期,在程序运行过程是不会清空的。

当然我们也可以加一个static 来改变生命周期。static char s[] = “hello world”;

 

 

扩展:static的作用:

  • 在修饰变量的时候,static修饰的静态局部变量只执行一次,而且延长了局部变量的生命周期,直到程序运行结束以后才释放。 初始化的时候自动初始化为0;

 

第二、static修饰全局变量的时候,这个全局变量只能在本文件中访问,不能在其它文件中访问,即便是extern外部声明也不可以。

第三、static修饰一个函数,则这个函数的只能在本文件中调用,不能被其他文件调用。