C 内存管理

一.内存中的数据位置

  1. 未初始化的全局变量(.bss段)
    (1)bss段用来存放那些没有初始化和初始化为0的全局变量
    (2)特点:bss类型的全局变量只占运行时的内存空间,而不占用文件空间
    (3)现代大多数操作系统,在加载程序时,会把所有的bss全局变量清0
    (4)作为全局变量,在整个程序的运行周期内,bss数据是一直存在的
    (5)为保证程序的可移植性,手工把这些变量初始化为0也是一个好习惯,这样这些变量都有个确定的初始值

  2. 初始化过的全局变量(.data)
    (1)data段用来存放那些初始化为非零的全局变量(如果数据全为零,编译器为了优化考虑,就会把它当bss段处理)
    (2)特点:data类型的全局变量即占文件空间,又占用运行时的内存空间
    (3)同样作为全局变量,在整个程序的运行周期内,data数据是一直存在的

  3. 常量数据(.rodata段)
    (1)rodata就是用来存放常量数据的,ro代表read only
    (2)常量是不能修改的,修改常量在Linux下会出现段错误
    (3)常量不一定就放在rodata里,有的立即数直接和指令编码在一起,存放在代码段(.text)
    (4)对于字符串常量,编译器会自动去掉重复的字符串,保证一个字符串在一个可执行文件(EXE/SO)中只存在一份复制
    (5)rodata是在多个进程间是共享的,这样可以提高运行空间利用率
    (6)把运行过程中不会改变的数据设为rodata的好处:在多个进程间共享,可以提高空间利用率,甚至不占用RAM空间
    (7)由于rodata在只读的内存页面中式受到保护的,任何试图对它的修改都会被及时发现,这样可以提高程序的稳定性

  4. 代码(.text段)
    (1)text段用于存放代码(如函数)和部分整数常量
    (2)它与rodata段很相似

  5. 栈(stack)
    (1)主要用来存放临时变量和函数参数
    (2)栈作为一种基本数据结构,可以用来实现函数的调用。
    (3)大多数编译器在优化时,会把常用的参数或局部变量放入寄存器中
    (4)但用栈来管理函数调用时的临时变量(局部变量和参数)是通用做法,3是辅助手段,且只在当前函数中使用,一旦调用下一层函数,这些值任然要存入栈中才行
    (5)要实现递归操作,不用栈不是不可能的,只是找不出比它更优雅的方式
    (6)栈向下(低地址)增长,每向栈中PUSH一个元素,栈顶就向低地址扩展,每从栈中POP一个元素,栈顶就向高地址回退

  6. 堆(heap)
    (1)堆存储区主要存储动态分配的内存块,它的生命周期完全由使用者控制。
    (2)如果使用者没有主动释放动态分配的空间,在程序运行结束时,操作系统会回收这部分内存。
    内存布局

  7. 栈空间与推空间的区别
    (1)栈空间用于存储函数参数和局部变量,所需空间由系统自动分配,回收也由系统管理,无须人工干预
    (2)堆空间用于存储动态分配的内存块,分配和释放空间均由程序员控制,有可能产生内存泄漏
    (3)栈空间作为一个严格后进先出的数据结构,可用空间永远都是一块连续的区域
    (4)堆空间在不断分配和释放过程中,可用空间链表频繁更新,造成可用空间逐渐碎片化,每块可用空间都很小
    (5)栈空间由计算机底层的支持,PUSH和POP都有专门的指令,效率高
    (6)堆空间通过函数动态获取空间,涉及可用空间链表的扫描和调整以及相邻可用空间的合并等操作,效率相对较低

二. 内存分配方式

内存分配方式有三种

  1. 从静态存储区域分配
    内存在程序编译时就已经分配好了,这块内存在程序的整个运行期间都存在,如全局变量、static变量等。
  2. 在栈上创建
    在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算使用内置于处理器的指令集,效率很高,但分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配
    程序在运行时用malloc或new申请所需要的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多

三.野指针

  1. 定义:野指针不是NULL指针,是指向”垃圾“内存的指针
  2. 野指针的成因主要有两种:(1)指针变量没有被初始化
    任何指针变量刚被创建时不会自动成为NULL指针,它会随机指向一个地址,但我们不知道这个地址是否能用。因此,指针变量在创建同时应当被初始化,要么将指针设置为NULL,要不就让它指向合法内存
    (2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针
    指针p经过free或者delete之后,它们只是指针所指的内存给释放掉,但并没有把指针本身干掉。如果使用调试器跟踪,发现指针p被free之后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,P指针成了”野指针“

四.内存泄漏

  1. 内存泄漏的定义:一种资源泄漏,是指计算机程序没有合理地管理已经分配的内存,导致不再使用的内存没有及时释放。
  2. 产生的原因:就是分配的内存没有及时回收,导致这块空间即无法使用也无人回收
    由于栈内存由编译器负责分配和回收,所以不存在内存泄漏问题;而堆内存由程序员负责分配和回收,正是这种人为控制导致了内存泄漏的发生
  3. 导致的结果:随着程序运行时间的增长,泄漏的内存越积越多,可用的内存越来越少,最终无法为程序分配新的内存,进而导致线程崩溃。
  4. 内存泄漏的预防:(1)最有效的方法就是我们自己良好的编程习惯, malloc和free,new和delete成对出现
    new和delete运算符的小心使用,如果是自定义类的话,要检查下里面有没有析构函数,new[]也要有delete[]
    (2)借助一些内存泄漏检测工具,eg:Valgrind作为一款功能丰富的调式工具集,其包含的Memcheck工具是一个强大的内存检查器,可以帮助我们检测各种内存问题,包括检测内存泄漏

五.常见的内存错误及对策

  1. 内存分配未成功,却使用了它
    (1)产生原因:因为没有意识到内存分配会不会成功。
    (2)常用的解决方法:在使用内存之前检查指针是否为NULL;如果指针p是函数的参数,那么在函数的入口处用assert (p != NULL)进行检查;如果使用malloc或new来申请内存,应该用if (p == NULL)if (p != NULL)进行防错处理

  2. 内存分配虽然成功,但是尚未初始化就引用它
    (1)产生原因:a. 是没有初始化的观念;b. 是误以为内存的默认初值全为零,导致引用初值错误(eg数组)
    (2)解决方法:内存的默认初值究竟是什么并没有统一的标志,尽管有些时候为零值,我们宁可信其无不可信其有,所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦

  3. 内存分配成功并且已经初始化,但操作越过了内存的边界(内存越界)
    (1)产生原因:eg 在使用数组时经常发生下标”多1“或者”少1“的操作,特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界;内存越界又称为内存访问越界,是指访问了所申请空间之外的内存
    (2) 越界操作导致程序崩溃的原理:由于通过下标对数组元素赋值时不会自动检测下标越界,因此程序会在相应的地址上进行赋值,这样造成的结果就是可能错误地修改了其他变量的值。
    (3) 解决方法:无法从根本上避免,只能预防。要对操作的内存空间要有一个明确认识,确保只修改程序分配的空间。

  4. 忘记释放内存
    (1)产生原因:人为因素最主要,就是忘记释放申请的内存空间,动态内存的申请与释放必须配对
    (2)解决方法:使用malloc和free,new和delete的使用次数一定要相同

  5. 释放内存却继续使用它,有三种情况
    (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱居民
    (2)函数的return语句写错了,注意不要返回指向”栈内存“的”指针“或者”引用“,因为该内存在函数体结束时被自动销毁
    (3)使用free或delete释放了内存后,没有将指针设置为NULL,导致产生野指针

六. 内存malloc,new与free,delete的相关问题

  1. malloc和new实现的机制
    (1)new的工作:调用了C++内建的operator new(对象的构造函数)分配内存,大小为A对象所占的内存大小;
    operator new再调用C接口的malloc函数去实现。
    同样new的对应delete也是同样,也是调用了C++内建的operator delete(分配对象的析构函数),operator delete再调用C接口的free函数。
    (2)malloc的实现机制:将所有的空闲空间内存块链成链表,每个节点记录空闲内存的地址,大小等信息;再分配内存时,找到大小合适的块,将该内存一分为二,一块大小与申请的空间大小一样,另一块就是剩下的(如果用户申请的内存大,链表找不到这么大的,就会检查各个内存块,将其合成比较大的内存块去使用)

  2. 为什么要少用malloc函数?
    当分配内存时,为了记录和管理分配出去的内存,额外多分配了不少内存,造成浪费,尤其是我们频繁的去申请小块内存时,造成的浪费更多。所以这就是为什么我们要少用malloc的原因。

  3. delete和free怎么知道要释放内存的大小?
    (1)真正的内存管理如申请或者释放等,不是交由malloc,free库函数去负责的,而是交给操作系统去完成,他们只是一个维护空闲的链表式的内存块。
    (2)就比如说malloc申请空间,申请的只是10个字节,但返回的内存大小大于10个字节。因为操作系统会多出分配内存去存储申请的内存大小,地址等信息;就有点像链表的头指针,这个节点存储的空间的首地址以及分配内存大小。当我们调用free的库函数时,其实它也不知道要释放多少内存,它只需要改变头结点里面的内存大小就行了,具体的空间释放交由操作系统去完成。

  4. 如何减少malloc的内存浪费?
    (1)解决方案:可以使用内存池
    (2)作用:减少malloc的次数
    (3)原理:用malloc申请一大块内存,当要分配的时候,从这一大块内存中一点一点的分配,当一大块内存分配得差不多的时候,再用malloc申请一大块内存,然后再一点一点的分配出来。
    (4)更新升级以及解决本身缺陷:可伸缩内存池

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值