在本文的開頭,我必須聲明這是一篇有目的性的文章。我有一個長輩最近得了尿毒癥,所以我聯系芯片之家的管理員并且得到他非常爽快的允許,在芯片之家的平臺將人體的腎功能與開發技術結合起來寫一篇文章發表在這里,希望得到更多人的幫助。所以,請記住:不管您是轉發本文,點在看,還是點擊原文輕眾籌給予幫助或者轉發,都是一次善舉。


程序的形態非常之多,不管是可以作為一個操作系統,還是作為一個 hello world,也不管是作為一個 app,還是作為一個嵌入式的固件。程序在本質上來說,是函數代碼與資源的集合體。我們的話題是,程序的資源,而且是程序中第一要素的資源——內存

 


如果說程序是一個人,那么骨架可以比喻成程序的架構,皮肉則是代碼,血液則是內存。在程序的運行過程當中,絕大部分的指令在執行與回寫操作,我必須聲明這是一篇有目的性的文章。所以我聯系芯片之家的管理員并且得到他非常爽快的允許,回寫階段都會操作到內存,可以說內存伴隨著程序執行的整個周期,就像是血液始終流轉在我們的肉體之中。那么在內存中進行垃圾回收的程序之“腎”,又是什么呢?

 

 

這是一段非常簡單的 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 也需要實現類似的方式進行自動回收,那么就需要對編譯器的預處理過程進行改造,并且在內存申請和釋放的庫函數之上維護一個內存的監控結構,去給內存塊做計數。

 

同時引用計數法法有一個非常大的缺點,就是循環引用會導致內存泄漏。如下代碼:

 

 

當函數執行完畢,a 與 b 相互引用。但是在棧中以及在數據段中已經沒有指針可以訪問到 a 與 b 的對象本身。也就是說程序已經失去了這兩塊內存的訪問權,但是它們兩者又相互指向,導致內存的計數無法歸零。所以一直不能釋放,導致了內存泄漏,形成了垃圾。

 

二、可達性分析法。

可達性分析法,顧名思義就是分析內存程序能否可以“達到”。也就是分析程序是否有失去對于內存的訪問權。程序在運行狀態中,內存時刻處于變化之中,猶如人體的血液流動不止。但是不管在任何時刻,我們的程序一定可以訪問的內存大概有 2 個類別:

 

1、數據段,也就是全局變量與靜態變量。

 

2、棧空間中未釋放的變量也就是當前入棧的動態局部變量。

 

可達性分析法需要依賴于 Runtime,也就是運行時環境,它們時刻監控著上面兩個大類內存中的指針變量或者引用,并且周期性地對這些指針或者引用的指向進行遍歷,并且是遞歸逐級地往下遍歷。整體而言是在遍歷一個以這兩大類內存中的指針變量和引用為入口的圖。只要能夠遍歷到的內存塊就可以進行可達性的標志。當程序進入垃圾回收周期,它會遍歷已經分配的所有內存,如果訪問到的內存塊擁有可達性標志,那么則跳過。如果沒有可達性標志,則可以釋放回收。這樣就可以避免類似引用計數算相互引用導致不歸零,但是不可達卻又不釋放的問題。如下圖,藍色內存塊是會被回收的。

 


然而,可達性分析算法是需要依賴于運行時環境的,也就是類似 java 那樣的虛擬機。所以目前 C/C++之類的語言還無法支持這種自動垃圾回收的判定算法。

 

所以說了那么多,我們對于這些程序語言的一個診斷是:

 

OC:apple 給它換了腎,但是腎不好,不過總體無礙。

 

java:腎很好啊。

 

C/C++:沒有腎的,需要程序員幫他做“腎透析”。

 

那么像 C/C++這么好的語言,我們能夠給它一個“腎”,讓它過上更加健康的生活嗎?

答案是有的。