编译器的自动内存管理,静态的GC算法( 二 )


有时候,申请的内存并不会在当前函数内释放,而是返回给更上层的主调函数 。
这时的GC算法,就需要跨越函数的调用链,进行指针分析 。

编译器的自动内存管理,静态的GC算法

文章插图
mat类的构造函数__init()
前面的mat对象的成员指针m3->data,就是需要跨函数分析的指针 。
它是在构造函数里申请的内存,因为是成员变量,所以要在析构函数里释放 。
如果是局部变量,就在当前函数内释放:因为局部变量的作用域就是当前函数 。
编译器的自动内存管理,静态的GC算法

文章插图
mat类的声明,成员变量部分
成员变量的有效时间,是伴随着当前对象的 。
局部变量的有效时间,是伴随着当前函数的 。
成员变量在构造函数返回时依然有效,所以要把它是malloc()申请的这个信息,传递到更上层的函数 。
这样:
1)在main()里才知道它是malloc()申请的,
2)在 dd = m3->data 时才知道给它指向的内存引用计数+1,
3)在释放m3时,析构函数把引用计数-1之后,引用计数才不为0:内存依然是有效的,这时指针dd依然指向它 。
否则,dd就是野指针了!
编译器的自动内存管理,静态的GC算法

文章插图
mat类的析构函数__release()
函数调用链,在语义分析时是很容易确定的 。
抽象语法树AST上的每一个函数调用,必然有一个主调函数、有一个被调函数 。
主调和被调,构成了整个程序的函数调用图:
最顶层的是main()函数,最底层的是malloc()函数 。
以malloc为起点、main为终点,做图的宽度优先搜索,就可以获取整个调用链 。
然后从离malloc最近的函数开始,一层层的分析就行了 。
编译器的自动内存管理,静态的GC算法

文章插图
函数调用图
一定是用图的宽度优先搜索(BFS)!
不能用深度优先搜索(DFS),因为一个上层函数可能调用多个下层函数,而这多个下层函数里都malloc了内存 。
如上图:
如果是DFS,分析顺序是A->D,这样D调用B而申请的内存就会被漏过去了 。
如果是BFS,分析顺序是A->B->C->D->E,这样任何函数申请的内存如果传递给上层,(在分析上层函数时)都不会被漏过去 。
4,递归调用的指针分析,
上图的C()和E()之间的互相调用构成递归,表现为函数调用图上的回路 。
这种情况下,两个函数里申请的内存会互相传递,属于最复杂的一种情况!
在编译器里的处理方法是:
do {
delivery = check_delivery();
} while (0 == delivery);
用do while循环检查内存的传递情况,记录传递的变量和计数,直到不再发生变化为止 。
最后,就是在合适的位置添加free()代码了:
最后的总是最简单的,the last is the simplest.
有兴趣了解细节的,可以看我写的scf编译器框架的GC算法 。
编译原理(龙书)里没有这方面的算法,这是我自己想出来的 。
听说又像牛顿跟莱布尼茨一样,跟rust的作者相见略同了是吧[捂脸]
我先起个直白的名字叫static GC.
老外就那样,有一点点的改进就猛吹[捂脸]
神经网络都能被辛顿吹成deep learning 。

【编译器的自动内存管理,静态的GC算法】


推荐阅读