内存管理

.bss段

  • .bss段用于存放那些没有初始化和初始化为0的全局变量(包括静态变量)。
  • .bss类型的全局变量只占用运行时的内存空间,而不占用文件空间
  • 当.bss段后面还有其他段时,它就可能占用文件空间,至少镜像文件是这样的
  • 操作系统地下,在加载程序时会把bss段清零
  • 不基于操作系统的裸机程序需要我们自己对bss段进行清零,有时候我们不用清零是因为厂商提供的前面的引导代码帮我们做过了这个操作

.data段

  • data段用来存放初始化为非零的全局变量,如果数据初始化为0,为了优化考虑,编译器会把它分配到bss段
  • data段既占用文件空间又占用运行时的内存空间

.rodata段

  • 用来存放常量数据,const关键字修饰的变量一般会被放在rodata段中
  • 常量不一定存放在rodata里,有的立即数直接和指令编码在一起,存放在代码段(.text)中
  • 字符串常量存放在rodata段,对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件中只存在一份复制
  • rodata是在多个进程间共享的,这样可以提供运行空间利用率
  • 在有的嵌入式系统中,rodata放在ROM(NOR Flash)里,运行时直接读取,无需加载到RAM内存中
  • 在嵌入式Linux系统中,也可以通过一种XIP(就地执行)的技术,也可以直接读取,而无需加在到内存中
  • 常量是不能修改的,修改常量在Linux下会出出现段错误

.text段

  • text段存放代码(如函数)和部分整数常量
  • text段与rodata段十分相似,主要不同在于这个段是可以执行的

  • 栈用来存放临时变量和函数参数
  • 没有栈是无法使用C语言的,因为函数调用等依赖于栈来保存和恢复现场
  • 尽管大多数编译器以及CPU内核在优化时,会把常用的参数或者局部变量放入寄存器中,但用栈来管理函数调用时的临时变量(局部变量和参数)是通用做法,前者只是辅助手段且只在当前函数中使用,一旦调用下一层函数,这些值仍然要存入栈中才行
  • 在CPU发送中断以及调度时,栈还负责保存和恢复现场
  • 栈分为4中,满减栈、满增栈、空减栈、空增栈。ARM中一般使用满减栈

  • 堆是最灵活的一种内存,它里面的变量的生命周期由使用者控制
  • 标准C语言使用malloc、realloc、free来操作堆空间

一般情况下,各个段以及堆栈的顺序为:.text、.rodata、.data、.bss、堆、栈 

内存分配方式

  • 从静态存储区域分配。内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在,如全局变量、static修饰的静态变量等。
  • 在栈上创建。在执行函数时,函数内部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算使用内置于处理器的指令集,效率很高,但分配的内存容量有限。该区域中申请到的变量的值是不确定的,建议在程序中手动给其一个初始化值。
  • 从堆上分配,即动态内存分配。使用malloc或new申请所需要的内存,使用free或delete释放内存。动态内存的生存期由程序员决定,使用灵活但容易出错。该区域申请到的内存空间中的值依然是不确定的,建议申请之后清零这部分区域。

野指针

  • 野指针不同于NULL指针,它是指向“垃圾”内存的指针。
  • 野指针是很危险的,无法用if语句区分它与“有效”指针
  • 野指针的成因有2中

(1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的值是随机的。建议指针变量在创建时为其显示的初始化为NULL或合法内存。

(2)指针被free或delete后,指针仍旧指向之前的地址,此时操作该指针是危险的,该指针也是野指针。建议在每次释放内存时都显示将该指针赋值为NULL。

常见内存错误及对策

  • 内存分配为成功,却使用了它。 对策:如果指针是函数的参数,那么在函数的入口处用if(p==NULL)先对该指针的值进行检查;如果是用malloc或者new来申请内存,也要用if(p==NULL)来进行判断是否分配内存成功。
  • 内存分配虽然成功,但是尚未初始化就引用它。 对策:先对分配得到的内存空间进行清零在使用。
  • 内存分配成功并已经初始化,但操作越过了内存边界。
  • 忘记了释放内存,造成内存泄漏。对策:在可能需要释放内存的每一个地方都判断指针是否是NULL,不是则释放内存,释放指针将该指针的值设为NULL。
  • 释放了内存却继续使用它。有以下3中情况

(1)使用free或delete释放内存后,没有将指针设置为NULL,产生了野指针。

(2)函数return后,注意不要返回指向栈内存的指针或者引用,因为该内存在函数执行结束后就被自动销毁了。

(3)不要试图用全局的指针变量指向栈内存,最好不要让栈内存和其他内存空间中的指针产生任何瓜葛。

指针与数组的对比

  • 数组名对应着(而不是指向)一块内存。其地址与容量在生命周期内保持不变,只有数组的内容可以改变。
  • 指针可以随时指向任意类型的内存块,它比数组灵活但也更危险。
  • 下面是一些容易出错的地方

(1)修改内容

a是一个局部变量数组它对应的这块空间存储在堆上,所以"hello"是存储在它对应的堆上的这块空间中的,是可以被更改的。p是一个局部指针变量,其本身的4个字节存储在堆上,它只是指向而不是对应其他内存空间,所以"world"应该会被存储到rodata段,是只读的,修改它会出现错误。

(2)内容复制比较

(3)计算容量

sizeof可以得到数组的大小,而不能得到指针所指向数组(以及其他任何类型)的大小。

指针本身占4个字节,所以sizeof(p)得到的是4

数组作为函数参数进行传递时,该数组自动转换为同类型的指针,所以在函数内部无法得知该数组的大小,只有通过传参的方式将数组大小传递给函数。

总结

  • bss段用来存储未初始化和初始化为0的变量包括静态变量,一般bss段是最后一个段时不占用文件空间,裸机开发时要考虑是否需要我们对bss段进行清零
  • data段用来存放初始化为非零的全局变量
  • rodata段用来存储const关键字修饰的变量以及字符串常量,这个段不能被修改,在一些嵌入式系统如单片机中,这个段时钟在ROM中
  • text段用来存储代码和部分立即数
  • 栈用来存放函数参数、非静态局部变量以及CPU中的一些寄存器的值用于保存和恢复现场。分为满减栈、满增栈、空减栈、空增栈。ARM中一般使用满减栈
  • 堆空间由用户来管理分配空间,使用malloc或者new来分配空间,free或者delete来释放空间。比较灵活但容易出错,注意不要忘记释放造成内存泄漏
  • 一般情况下,各个段以及堆栈的顺序为:.text、.rodata、.data、.bss、堆、栈
  • 内存分配来源这3个部分:静态存储区(生命周期=程序生命周期,全局变量和静态变量)、栈(生命周期=函数生命周期,函数参数和非静态局部变量)、堆(生命周期由程序员确定,malloc和free)
  • 野指针的来源:未初始化的指针、释放内存后的指针
  • 任何时候保证程序中只存在NULL指针和有效指针
  • 避免出错的方法:

定义指针时初始化为NULL或安全有效的地址

函数参数中若包含指针,在函数最前面使用if(p==NULL)判断指针的值

动态分配内存时使用if(p==NULL)判断申请是否成功

局部变量以及动态分配成功的内存空间,在使用前先进行清零

在程序中可能释放内存的地方,释放内存时先if(p==NULL)判断指针是否为空,非空的话先释放内存再将指针设置为NULL

不要让非栈空间上的指针指向栈空间

栈空间上的指针指向栈空间时,不要试图return返回该指针

  • 数组是有自己的内存空间的,而指针没有。注意区分数组初始化和指针指向的区别。另外数组作为函数参数时会自动转换为同类型的指向该区域的指针进行传递

参考《高质量嵌入式Linux C编程》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值