程序也有“肾”,你知道是什么吗?

关注、星标公众号,直达精彩内容

ID:chiphome-dy

作者:程世辉

整理排版:晓宇

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

如果说程序是一个人,那么骨架可以比喻成程序的架构,皮肉则是代码,血液则是内存。在程序的运行过程当中,绝大部分的指令在执行与回写操作,回写阶段都会操作到内存,可以说内存伴随着程序执行的整个周期,就像是血液始终流转在我们的肉体之中。那么在内存中进行垃圾回收的程序之“肾”,又是什么呢?

int* a;
int b =2;
main()
{
    static int c;
    {
        int d = 9;
        char* e =  malloc(10);
        printf("d=%d\r\n",d);
    }
}

这是一段非常简单的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也需要实现类似的方式进行自动回收,那么就需要对编译器的预处理过程进行改造,并且在内存申请和释放的库函数之上维护一个内存的监控结构,去给内存块做计数。

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

fun()
{
    A* a = [ A new];
    A* a1 = a;
    B* b = [B new];
    B* b1 =  b; 
             a->b = b;
             b ->a = a;
}

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

二、可达性分析法。

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

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

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

可达性分析法需要依赖于Runtime,也就是运行时环境,它们时刻监控着上面两个大类内存中的指针变量或者引用,并且周期性地对这些指针或者引用的指向进行遍历,并且是递归逐级地往下遍历。整体而言是在遍历一个以这两大类内存中的指针变量和引用为入口的图。只要能够遍历到的内存块就可以进行可达性的标志。

程序进入垃圾回收周期,它会遍历已经分配的所有内存,如果访问到的内存块拥有可达性标志,那么则跳过。如果没有可达性标志,则可以释放回收。这样就可以避免类似引用计数算相互引用导致不归零,但是不可达却又不释放的问题。如下图,蓝色内存块是会被回收的。

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

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

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

java:肾很好啊。

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

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

答案是有的。

推荐阅读:

嵌入式编程专辑Linux 学习专辑C/C++编程专辑
Qt进阶学习专辑关注微信公众号『技术让梦想更伟大』,后台回复“m”查看更多内容,回复“加群”加入技术交流群。
长按前往图中包含的公众号关注

猜你喜欢

转载自blog.csdn.net/u012846795/article/details/108656583