Obstack介紹
Obstack初始化
在Obstack中申請對象
釋放對象
申請growing object
獲取Obstack狀態
數據對齊
Obstack是C標准庫裡面對內存管理的GNU擴展(實際上就是GNU C library了)。Obstack===Object stack。沒錯,Obstack就是一個棧,棧裡面的元素是對象object(不是面向對象的對象哦,這裡的對象單指數據元素)。這些數據是動態的,也就是使用的是動態內存。這種內存管理技術屬於Region-based memory management。
那什麼叫基於區域的內存管理呢?區域指的是我們申請的數據對象存放的位置。在這種內存管理模式下,我們將所有申請的數據對象集中放在一起(當然咯,地址不一定連續。集中在一起是一種邏輯結構,比如Obstack的棧)。好處就是,可以一次性的釋放內存,集中管理。這下你對Obstack有了大體上的了解了吧。
Obstack是一種內存池,內存池裡面包含了數據對象棧。
啥叫內存池呢?內存池也叫做固定大小的塊內存申請(fixed-size blocks allocation),同樣也是一種內存管理技術。這種技術允許動態申請內存。(是不是迷糊了?前面還說固定,現在又說動態了。實際上固定的是總大小,動態是指內存實際申請使用。如果你用過virtual box,沒錯就是那個百分之60剛玩linux的人都會安裝的東西。裡面創建虛擬硬盤時對動態分配有這樣的描述:虛擬磁盤只是逐漸占用物理硬盤空間[直至達到分配的大小],不過當其內部空間不用時不會自動縮減暫用的物理硬盤空間])這種方式有點類似malloc或者C++的new操作。但是malloc和new操作都會造成內存碎片化問題(別打我,這是wiki講的,不關我的事)。更有效地方式就是使用相同大小內存池了。預先申請,集中管理。應用程序在內存池裡面自由的玩耍,從來不上岸-_-|||。
回過頭繼續講Obstack。你可以創建任意數量的獨立的Obstack,然後在這些Obstack裡面申請對象。這些對象遵循棧的邏輯結構:最後申請的對象必須第一個釋放。不同的Obstack裡面的數據是相互獨立的,沒有任何關系。除了對內存釋放順序的要求之外,obstack是非常通用的:一個Obstack可以包含任意數量任意尺寸的對象。
Obstack裡面內存申請的實現用的一般都是宏,很有效率吧。如果你不想使用宏(理由是:方便調試),可以看一看我的另一篇文章。這篇文章的最後介紹了如何避免使用預定義的宏。唯一的內存空間損耗就是不同對象之間可能需要內存填充,讓每一個對象都起始於合適的邊界線,這一點實際上是用空間換取時間。
Obstack的表現形式是一個結構體:struct obstack。這個結構有一個非常小的固定大小,記錄了obstack的狀態,以及如何找到該Obstack裡的對象。但是呢,obstack結構體自身不包含任何對象,別妄想直接探尋struct obstack的內容了。必須使用規定的函數才行,這點沒得商量。
你可以通過聲明一個struct obstack類型的變量來創建obstack。或者動態申請struct obstack *obstack = (struct obstack *)malloc(sizeof(struct obstack)); 你還可以obstack裡面使用obstack(然並卵,GNU C library文檔自己說的)
Obstack使用的函數都需要指定使用的是那個obstack。Obstack裡面的對象會被打包成一個大的內存塊(叫做chunks)。struct obstack結構指針指向當前使用的chunks。
每當你申請的對象無法塞進之前的chunk時,都會創建一個新的chunk。這些chunk以何種形式連接在一起(鏈表?樹?),不是我們關心的啦。這些交由obstack自動管理。chunk由obstack自動創建,但創建的方式得由你來決定,一般會直接或間接的用到malloc函數。
聯想我們之前講到的內存池:chunk是固定大小的池。Obstack只是一種池的管理員:他告訴來洗澡的人必須遵守規定,最後來的必須先走。
#include<obstack.h> int obstack_init(struct obstack *obstack-ptr)
obstack_init實際上是一個宏實現。obstack_init會自動調用obstack_chunk_alloc函數來申請chunk,注意這還是個宏,需要你指定這個宏指向的函數。如果內存申請失敗了會調用obstack_alloc_failed_handler指向的函數,沒錯依舊是宏。如果chunk裡面的對象都被釋放了,obstack_chunk_free指向的函數被用來返回chunk占用的空間。目前版本的obstack_init永遠只返回1(之前的版本會在失敗的時候返回0)
實例:
#include<obstack.h> #include<stdlib.h> #define obstack_chunk_alloc malloc #define obstack_chunk_free free //靜態申請obstack static struct obstack myobstack; obstack_init(&myobstack); //動態申請obstack struct obstack *myobstack_ptr = (struct obstack*) malloc (sizeof (struct obstack)) obstack_init(myobstack_ptr)
obstack chunk的大小默認情況下為4096。如果你想自定義chunk的大小,需要使用宏obstack_chunk_size
int obstack_chunk_size (struct obstack* obstack-ptr)
注意這實際上是個左值(C++裡面有麻煩的左值引用,右值引用 囧rz)。obstack-ptr如之前的myobstack所示,指明了是哪個obstack。如果你適當的提高chunk的大小,有利於提高效率。減小則沒啥好處。。。
if (obstack_chunk_size (myobstack_ptr) < new-chunk-size) obstack_chunk_size (myobstack_ptr) = new-chunk-size
最直接的方法使用aobstack_alloc函數
void * obstack_alloc (struct obstack *obstack-ptr, int size)
調用方式和malloc差不多, 對新申請的空間不初始化。如果chunk被object填滿了,obstack_alloc會自動調用obstack_chunk_alloc。
struct obstack string_obstack; /* .... 初始化內容 .... */ char * copystring (char *string) { size_t len = strlen (string) + 1; char *s = (char*) obstack_alloc (&string_obstack, len); memcpy (s, string , len); return s; }
如果想在申請時初始化,需要用到obstack_copy或者obstack_copy0
void * obstack_copy (struct obstack *obstack-ptr, void *address, int size) //使用從地址address開始復制size大小的數據初始化,新申請的object內存大小也為size void *obstack_copy0 (struct obstack *obstack-ptr, void *address, int size) //同obstack_copy但結尾處額外添加一個空字符,在復制以NULL結尾的字符串時很方便。
同樣很簡單,我們使用函數obstack_free
void obstack_free (struct obstack *obstack-ptr, void *object)
如果object是空指針的話,所有的對象都被釋放,留下一個未初始化的obstack,然後obstack庫會自動釋放chunk。如果你指定了object地址,那自這個object之後的所有對象都被釋放。值得注意的是:如果你想釋放所有object但同時保持obstack有效,可以指定第一個object的地址
obstack_free (obstack_ptr, first_object_allocated_ptr);
由於obstack chunk的內存空間是連續的,在其中的對象空間可以一步步搭建一直增加到結尾。這種技術稱之為growing object。盡管obstack可以使用growing object,但是你無法為已申請好的對象使用這項技術(理由是如果這對象之後還有對象,會造成沖突)
void obstack_blank (struct obstack *obstack-ptr, int size) //添加一個為初始化的growing object void obstack_grow (struct obstack *obstack-ptr, void *data, int size) //添加一個初始化的空間,類似與obstack_copy void obstack_grow0 (struct obstack *obstack-ptr, void *data, int size) //obstack_grow類似與obstack_copy, 那obstack_grow0你說類似與誰? void obstack_lgrow (struct obstack *obstack-ptr, charc) //一次添加一個字符(字節) void obstack_ptr_grow (struct obstack *obstack-ptr, void *data) //一次添加一個指針。。。大小為(sizeof(void *) void obstack_int_grow (struct obstack *obstack-ptr, int data) //一次添加一個int型數據,大小為sizeof(int)
在growing object中最重要的函數是obstack_finish, 因為只有調用了本函數之後才會返回growing object 的最終地址。
void *obstack_finish (struct obstrack *obstrack-ptr)
如果你想獲取growing object當前大小,可以使用obstack_object_size
int obstack_object_size (struct obstack *obstrack-ptr) //只能用在obstack_finish之前,否則只會返回0
如果你想取消一個正在增長的對象,必須先結束他,然後再釋放。示例如下:
obstack_free (obstack_ptr, obstack_finish (obstack_ptr))
上面的函數在添加數據時會檢查是否由足夠的空間。如果你只增加一丁點的空間,檢查這一步驟顯然是多余的。我們可以省略這一步驟,更快的growing。這裡關於growing object額外在介紹一個函數:
int obstack_room (struct obstack *obstack-ptr) //返回可以安全添加的字節數
其余的快速添加函數只需要在之前的growing object函數添加後綴_fast即可(如果不清楚可以查看GNU C 關於這部分的介紹)
以下函數用於獲取obstack的狀態:
void * obstack_base (struct obstack *obstack-ptr) //返回正在增長的對象的假定起始地址。為啥是假定呢,因為如果你增長的過大,當前chunk的空間不夠,obstack就會新創建一個chunk,這樣地址就變了。 void *obstack_next_free (struct obstack *obstack-ptr) //返回當前chunk未被占用的第一個字節的地址 int obstack_object_size (struct obstack *obstack-ptr) //返回當前growing object的大小。等同與: //obstack_next_free (obstack-ptr) -obstack_base (obstack-ptr)
int obstack_alignment_mask (struct obstack *obstack-ptr)
宏展開後是一個左值。如果是函數實現,返回的是掩碼。你給他復制也應該是掩碼。掩碼的值應該是2的n次方減一,換算後也就是說地址必須是2的n次方的倍數。如果你改變了掩碼,只有下次申請object的時候才會生效(特例是growing object:立即生效,調用obstack_finish後就能看到效果了)
本文地址http://www.cnblogs.com/san-fu-su/p/5739780.html