全局变量需要压栈吗_你知道程序的“肾”是什么吗?

在本文的开头,我必须声明这是一篇有目的性的文章。我有一个长辈最近得了尿毒症,所以我联系芯片之家的管理员并且得到他非常爽快的允许,在芯片之家的平台将人体的肾功能与开发技术结合起来写一篇文章发表在这里,希望得到更多人的帮助。所以,请记住:不管您是转发本文,点在看,还是点击原文轻众筹给予帮助或者转发,都是一次善举。

程序的形态非常之多,不管是可以作为一个操作系统,还是作为一个 hello world,也不管是作为一个 app,还是作为一个嵌入式的固件。程序在本质上来说,是函数代码与资源的集合体。我们的话题是,程序的资源,而且是程序中第一要素的资源——内存。

7bc01d12a769c5454798c70f3f8f70de.png

如果说程序是一个人,那么骨架可以比喻成程序的架构,皮肉则是代码,血液则是内存。在程序的运行过程当中,绝大部分的指令在执行与回写操作,我必须声明这是一篇有目的性的文章。所以我联系芯片之家的管理员并且得到他非常爽快的允许,回写阶段都会操作到内存,可以说内存伴随着程序执行的整个周期,就像是血液始终流转在我们的肉体之中。那么在内存中进行垃圾回收的程序之“肾”,又是什么呢?

25a9ce1677ea89ccc6a2872b4da829e3.png

这是一段非常简单的 C 语言代码。对于稍微有点基础的人都知道在这段程序中,每一个变量所占用的内存位置。

首先全局变量与静态变量是放在数据段(RW 段,其中未初始化的放在 ZI 段,由程序启动的时候统一清内存)比如:a,b,c;

局部变量放在栈空间中,比如:d,e;

同时还申请了一段存放于堆的内存,但是代码中并未使用 free 函数进行释放。

根据内存的特性我们知道,对于 a,b,c 等数据段的变量,它们是常住内存的,生命周期是永久的。对于栈里面的局部变量 d,e。它们的生命周期仅仅在“{}”之内,伴随着栈操作的 push 以及 pop 指令,创建和消亡。

程序中当 e 消亡在花括号外后,在堆中申请的内存就失去了指针对它的指向导致了内存泄漏。如果是在简单的程序中,这样的情况处理起来还是比较简单的,我们只要在程序后面采用 free 函数释放内存就可以。但是如果在拥有复杂的逻辑程序,这样动态申请的内存就需要花不少心思去管理。这个就是为啥很多 java 之类的高级语言在制作教程的时候都会在与 C/C++比较时经常强调,java 没有指针并且拥有垃圾自动回收机制,会显得更加安全,程序的健壮性更加容易得到保证。(当然 C/C++也可以写出健壮的程序,只是有些东西没那么方便)。这种可以自动帮助程序进行内存自动垃圾回收的机制就是程序的“肾”了。

那么为啥 C/C++到现在都不支持垃圾自动回收机制呢?我们可以从自动垃圾回收机制的原理去寻到答案。首先说一下自动垃圾回收的判定算法,一般常用的是两个:

一、引用计数法。

所谓的引用计数法,顾名思义就是在内存的描述结构体内部采用一个计数变量进行计数。每当有指针或者引用指向该内存块的时候,该内存块的描述结构体内部的计数器就递增。当指针或者引用被释放或者改变的时候就递减。当内存块的计数递减到 0 的时候,就可以释放回收该内存块了。

引用计数法,应该说是最简单实现内存可回收判定的算法。采用该算法实现自动回收机制的典型的有 apple 开发平台 Object-C 支持的 ARC机制。这种自动垃圾回收算法的实现有一个依赖和一个缺点。它的依赖就是需要编译器自动插入计数代码。想 OC 在 xcode 平台开发程序,它的编译环境会自动地插入手动进行计数的函数 retain,release 这样的语句。所以这个实现自动垃圾回收的本质还是让编译器做手动该做的事情而已。如果说 C 也需要实现类似的方式进行自动回收,那么就需要对编译器的预处理过程进行改造,并且在内存申请和释放的库函数之上维护一个内存的监控结构,去给内存块做计数。

同时引用计数法法有一个非常大的缺点,就是循环引用会导致内存泄漏。如下代码:

97c393255f918ace7d026966c0be6ae6.png

当函数执行完毕,a 与 b 相互引用。但是在栈中以及在数据段中已经没有指针可以访问到 a 与 b 的对象本身。也就是说程序已经失去了这两块内存的访问权,但是它们两者又相互指向,导致内存的计数无法归零。所以一直不能释放,导致了内存泄漏,形成了垃圾。

二、可达性分析法。

可达性分析法,顾名思义就是分析内存程序能否可以“达到”。也就是分析程序是否有失去对于内存的访问权。程序在运行状态中,内存时刻处于变化之中,犹如人体的血液流动不止。但是不管在任何时刻,我们的程序一定可以访问的内存大概有 2 个类别:

1、数据段,也就是全局变量与静态变量。

2、栈空间中未释放的变量也就是当前入栈的动态局部变量。

可达性分析法需要依赖于 Runtime,也就是运行时环境,它们时刻监控着上面两个大类内存中的指针变量或者引用,并且周期性地对这些指针或者引用的指向进行遍历,并且是递归逐级地往下遍历。整体而言是在遍历一个以这两大类内存中的指针变量和引用为入口的图。只要能够遍历到的内存块就可以进行可达性的标志。当程序进入垃圾回收周期,它会遍历已经分配的所有内存,如果访问到的内存块拥有可达性标志,那么则跳过。如果没有可达性标志,则可以释放回收。这样就可以避免类似引用计数算相互引用导致不归零,但是不可达却又不释放的问题。如下图,蓝色内存块是会被回收的。

ba4f33ad01c6c4c1460409f7dff58136.png

然而,可达性分析算法是需要依赖于运行时环境的,也就是类似 java 那样的虚拟机。所以目前 C/C++之类的语言还无法支持这种自动垃圾回收的判定算法。

所以说了那么多,我们对于这些程序语言的一个诊断是:

OC:apple 给它换了肾,但是肾不好,不过总体无碍。

java:肾很好啊。

C/C++:没有肾的,需要程序员帮他做“肾透析”。

那么像 C/C++这么好的语言,我们能够给它一个“肾”,让它过上更加健康的生活吗?

答案是有的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值