青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

posts - 297,  comments - 15,  trackbacks - 0
Arpan Sen, 技術(shù)負(fù)責(zé)人, Synapti Computer Aided Design Pvt Ltd
Rahul Kumar Kardam (rahul@syncad.com), 高級軟件工程師, Synapti Computer Aided Design Pvt Ltd

簡介:  代碼的性能優(yōu)化是一項(xiàng)非常重要的工作。經(jīng)常可以看到,采用 C 或 C++ 編寫的、功能正確的軟件
在執(zhí)行時耗費(fèi)大量的內(nèi)存、時間、或者在最糟的情況下既耗內(nèi)存又費(fèi)時間。作為一名開發(fā)人員,可以使用
C/C++ 提供的功能強(qiáng)大的工具來改進(jìn)處理時間,并且防止內(nèi)存破壞,這些工具其中之一是控制如何在代碼
中分配或者釋放內(nèi)存。通過介紹如何針對特定的情況創(chuàng)建自己的內(nèi)存管理器,本教程對內(nèi)存管理的相關(guān)
概念進(jìn)行了揭秘。

開始之前

了解本教程中包含的內(nèi)容以及如何最好地利用本教程。

關(guān)于本教程

本教程采用了一種基本方法為任何應(yīng)用程序構(gòu)建內(nèi)存管理器。本教程解釋了為什么需要內(nèi)存管理器,并提供
了為應(yīng)用程序編寫自定義的內(nèi)存管理器(以滿足特定的需求)的方法。

目標(biāo)

在本教程中,您將了解在設(shè)計內(nèi)存管理器之前需要考慮的一些注意事項(xiàng)、可用于創(chuàng)建這種內(nèi)存管理器的一些
特定技術(shù),并在文章的最后了解創(chuàng)建內(nèi)存管理器的方法。您還將了解各種類型內(nèi)存管理器設(shè)計的優(yōu)點(diǎn)和不足
之處。

先決條件

本教程面向那些入門級到中級水平的 Linux® 或 UNIX® 程序員。您應(yīng)該對使用 UNIX 命令行 Shell,
以及 C/C++ 語言的應(yīng)用知識有基本的了解。此外,還需要了解 malloccallocfreememcpy 和 
memset 等系統(tǒng)調(diào)用(即處理內(nèi)存分配、釋放和內(nèi)容修改的例程)的內(nèi)部工作方式。

系統(tǒng)要求

要運(yùn)行本教程中的示例,您需要具備安裝了 g++ 編譯器工具鏈的 Linux 或者 UNIX 系統(tǒng)。還需要具有
足夠大的 RAM(大約 256 MB)。

為什么要創(chuàng)建自定義的內(nèi)存管理器呢?

要理解內(nèi)存分配控制如何幫助提高代碼的運(yùn)行速度,首先需要回憶一下 C/C++ 中內(nèi)存管理的基礎(chǔ)知識。
C 中的標(biāo)準(zhǔn)庫函數(shù)mallocfreecalloc 和 realloc,以及 C++ 中的 newnew [ ]delete 和 delete
[ ]
 操作符,是這兩種語言中內(nèi)存管理的關(guān)鍵之處。在了解了這一點(diǎn)之后,有幾個內(nèi)容值得注意。

如 malloc 和 new 函數(shù)是通用的內(nèi)存分配器。您的代碼可能是單線程的,但它所鏈接的 malloc 函數(shù)同樣
可以處理多線程的范例。正是由于這個額外功能,使得這些例程的性能有所降低。

在執(zhí)行時,malloc 和 new 將向操作系統(tǒng)內(nèi)核請求內(nèi)存,而 free 和 delete 則請求釋放內(nèi)存。這意味著,
操作系統(tǒng)必須在每次提出內(nèi)存請求時在用戶空間代碼和內(nèi)核代碼之間進(jìn)行切換。反復(fù)調(diào)用 malloc 或者
 new 的程序,最終將由于不斷地進(jìn)行上下文切換而導(dǎo)致運(yùn)行緩慢。

對于在程序中分配的、并且以后不再需要使用的內(nèi)存,常常會忘記對其進(jìn)行刪除,并且 C/C++ 并沒有
提供自動的垃圾收集。這將導(dǎo)致程序的內(nèi)存空間占用進(jìn)一步增長。在實(shí)際的大型程序中,性能將受到顯著
的影響,因?yàn)榭捎脙?nèi)存變得越來越少,并且硬盤訪問是非常耗時的。

設(shè)計目標(biāo)

您的內(nèi)存管理器應(yīng)該滿足下面的設(shè)計目標(biāo):

  • 速度
  • 健壯性
  • 用戶使用便利性
  • 可移植性

速度

與編譯器提供的分配器相比,內(nèi)存管理器的速度必須更快一些。重復(fù)的分配和釋放不應(yīng)該降低代碼的執(zhí)行
速度。如果可能的話,應(yīng)該對內(nèi)存管理器進(jìn)行優(yōu)化,以便處理代碼中頻繁出現(xiàn)的某些分配模式。

健壯性

在程序終止之前,內(nèi)存管理器必須歸還它向系統(tǒng)請求的所有內(nèi)存。也就是說,不應(yīng)該存在內(nèi)存泄漏。它還
應(yīng)該能夠處理錯誤的情況(例如,請求過大的內(nèi)存),并且很好地解決這些問題。

用戶使用便利性

在將內(nèi)存管理器集成到他們的代碼中時,用戶應(yīng)該只需要更改很少的代碼。

可移植性

應(yīng)該可以很容易地將內(nèi)存管理器移植到其它的系統(tǒng),并且不應(yīng)該使用與平臺相關(guān)的內(nèi)存管理特性。

創(chuàng)建內(nèi)存管理器的實(shí)用策略

在創(chuàng)建內(nèi)存管理器時,下面的這些策略是很實(shí)用的:

  • 請求較大的內(nèi)存塊。
  • 對常見的請求大小進(jìn)行優(yōu)化。
  • 在容器中收集刪除的內(nèi)存。

請求較大的內(nèi)存塊

最常見的內(nèi)存管理策略之一是,在程序啟動期間請求一些較大的內(nèi)存塊,然后在代碼執(zhí)行期間反復(fù)地
使用它們。可以從這些內(nèi)存塊劃出部分內(nèi)存,以滿足各種數(shù)據(jù)結(jié)構(gòu)的內(nèi)存分配請求。這將極大地減少
系統(tǒng)調(diào)用的次數(shù),并提高執(zhí)行性能。

對常見的請求大小進(jìn)行優(yōu)化

在任何程序中,某些特定的請求大小將比其它大小的請求更加常見。如果對您的內(nèi)存管理器進(jìn)行優(yōu)化
以便更好地處理這些請求,那么它將工作得更加出色。

在容器中收集刪除的內(nèi)存

應(yīng)該將程序執(zhí)行期間刪除的內(nèi)存收集到容器中。然后,應(yīng)該使用這些容器來滿足進(jìn)一步的內(nèi)存請求。
如果某個請求失敗,那么應(yīng)該將內(nèi)存訪問委托給程序啟動期間分配的某個較大的內(nèi)存塊。雖然內(nèi)存
管理最初用于加速程序的執(zhí)行和防止內(nèi)存泄漏,但這種技術(shù)可能會潛在地導(dǎo)致程序的較低內(nèi)存空間
占用,這是因?yàn)樗梢灾赜脛h除的內(nèi)存。這是編寫您自己的內(nèi)存分配器的另一個原因!

分析 C++ new/delete 操作符的執(zhí)行時間

我們將從一個簡單示例開始。假定您的代碼使用了一個稱為 Complex 的類(該類用于表示復(fù)數(shù)),
并且它使用了 new 和 delete 操作符所提供的機(jī)制,如清單 1 中所示。

class Complex 
  {
  public:
  Complex (double a, double b): r (a), c (b) {}
  private:
  double r; // Real Part
  double c; // Complex Part
  };  
  
int main(int argc, char* argv[]) 
  {
  Complex* array[1000];
  for (int i = 0;i  <  5000; i++) {
    for (int j = 0; j  <  1000; j++) {
      array[j] = new Complex (i, j); 
      }   
    for (int j = 0; j  <  1000; j++) {
      delete array[j];
      }   
    }   
  return 0;
  }          
清單 1. Complex 類的 C++ 代碼


外層循環(huán)的每次迭代都會導(dǎo)致 1000 次分配和釋放。5000 次這樣的迭代將導(dǎo)致 10 百萬次用戶和
內(nèi)核代碼之間的切換。在 Solaris 10 計算機(jī)中使用 gcc-3.4.6 進(jìn)行編譯之后,執(zhí)行這個測試程序
平均需要花費(fèi) 3.5 秒。這是編譯器提供的全局 new 和 delete 操作符實(shí)現(xiàn)的基準(zhǔn)性能度量。要為 
Complex 類創(chuàng)建自定義的內(nèi)存管理器以改進(jìn)編譯器的實(shí)現(xiàn),您需要重寫 Complex 類特定的 new 和 
delete操作符。

New/Delete:深入研究

在 C++ 中,對內(nèi)存管理進(jìn)行組織實(shí)際上就是重載 new 或者 delete 操作符。代碼中不同的類可能
需要使用不同的內(nèi)存分配策略,這意味著每個類需要特定的 new。否則,必須重寫 new 或者 delete 
全局操作符。可以采用這兩種形式中的任何一種來實(shí)現(xiàn)操作符重載,如清單 2 中所示。


清單 2. 重載 new 或者 delete 操作符
void* operator new(size_t size);
void   operator delete(void* pointerToDelete); 
-OR-
void* operator new(size_t size, MemoryManager& memMgr); 
void   operator delete(void* pointerToDelete, MemoryManager& memMgr);

經(jīng)過重寫的 new 操作符負(fù)責(zé)分配第一個參數(shù)中指定大小的原始內(nèi)存,而 delete 操作符則負(fù)責(zé)釋放這
個內(nèi)存。請注意,這些例程只用于分配或者釋放內(nèi)存;它們并不會分別調(diào)用構(gòu)造函數(shù)或析構(gòu)函數(shù)。對
于由 new 操作符分配的內(nèi)存,將調(diào)用構(gòu)造函數(shù),而只有在調(diào)用了對象的析構(gòu)函數(shù)后才調(diào)用 delete 
作符。

new 的第二種變體是 placement new 操作符,它接受一個 MemoryManager 參數(shù),該參數(shù)基本上是為某
個對象分配原始內(nèi)存的數(shù)據(jù)結(jié)構(gòu),最后將調(diào)用這個對象的構(gòu)造函數(shù)。對于本教程,我們建議使用 new 或
者 delete 的第一種變體,因?yàn)楹笠环N變體將導(dǎo)致用戶代碼中 MemoryManager& 參數(shù)的大量增加,違反
了用戶使用便利性的設(shè)計目標(biāo)。

我們使用 MemoryManager 類,以便將 new 和 delete 操作符例程作為下面的 MemoryManager 例程的包裝,
從而進(jìn)行實(shí)際的內(nèi)存分配或者釋放,如清單 3 中所示: MemoryManager gMemoryManager;
// Memory Manager, global variable

清單 3. 作為包裝的 new、new[ ]、delete 和 delete[ ] 操作符

MemoryManager gMemoryManager; // Memory Manager, global variable

void* operator new (size_t size) 
  {
  return gMemoryManager.allocate(size);
  }

void* operator new[ ] (size_t size)
  {
  return  gMemoryManager.allocate(size);
  }

void operator delete (void* pointerToDelete)
  {
  gMemoryManager.free(pointerToDelete);
  }

void operator delete[ ] (void* arrayToDelete)
  {
  gMemoryManager.free(arrayToDelete);
  }      

注意:

  • 傳遞給 new[ ] 操作符的 size 是數(shù)組每個元素的大小與數(shù)組元素個數(shù)相乘后所得的大小。
  • 這個指針并不是在任何類特定的 newdeletenew[ ] 或者 delete[ ] 操作符中都可以
    使用。實(shí)際上,這四個例程都用作靜態(tài)方法。您必須在設(shè)計過程中始終牢記這一點(diǎn)。
  • 除了使用全局變量來聲明內(nèi)存管理器之外,您當(dāng)然還可以使用一個單例。

根據(jù)到目前為止的介紹,清單 4 顯示了內(nèi)存管理器類的基本接口。


清單 4. 內(nèi)存管理器接口
class IMemoryManager
  {
  public:
    virtual void* allocate(size_t) = 0;
    virtual void   free(void*) = 0;
  };
class MemoryManager : public IMemoryManager
  {
  public: 
    MemoryManager( );
    virtual ~MemoryManager( );
    virtual void* allocate(size_t);
    virtual void   free(void*);
  }; 
我們還傾向于將 allocate 和 free 例程作為內(nèi)聯(lián)例程,以便實(shí)現(xiàn)更快的分派。

單線程環(huán)境中 Complex 類型的第一個內(nèi)存管理器

我們設(shè)計了本教程的第一個內(nèi)存管理器,并始終牢記到目前為止已經(jīng)介紹的那些原則。出于
簡單的考慮,這個自定義的內(nèi)存管理器專門用于 Complex 類型的對象,并且只能在單線程
環(huán)境中工作。其基本思想是,在可用的內(nèi)存管理器中保存 Complex 對象池,并通過這個池來
完成以后的分配工作。如果需要創(chuàng)建的 Complex 對象的數(shù)目超過了該池中對象的數(shù)目,那么
將對該池進(jìn)行擴(kuò)展。刪除的對象將歸還到這個池中。圖 1 很好地說明了所發(fā)生的事件。


圖 1. 創(chuàng)建 Complex 對象的池
塊圖圖像 

該池中的每個塊具有兩個用途:

  • 它存儲了一個 Complex 對象。
  • 它必須能夠?qū)⑵渥陨磉B接到池中的下一個塊。

沒有選擇在 Complex 數(shù)據(jù)結(jié)構(gòu)中存儲一個指針,因?yàn)檫@將增加設(shè)計的整體內(nèi)存空間占用。相
比較而言,更好的方法是將 Complex 數(shù)據(jù)結(jié)構(gòu)的私有變量包裝到一個結(jié)構(gòu)中,并將其與 Complex
 指針一起創(chuàng)建一個聯(lián)合。當(dāng)用作池中的一部分時,這個指針用于指向該池中的下一個空閑元素。
當(dāng)用作獨(dú)立 Complex 對象時,可以利用該結(jié)構(gòu)來保存實(shí)數(shù)和復(fù)數(shù)部分。清單 5 顯示了經(jīng)過修改的
數(shù)據(jù)結(jié)構(gòu)。


清單 5. 經(jīng)過修改的數(shù)據(jù)結(jié)構(gòu),可以在不產(chǎn)生額外開銷的情況下存儲 Complex*
class Complex                     
  {
  public:
    Complex (double a, double b): r (a), c (b) {}
    private:
    union { 
      struct { 
        double r; // Real Part
        double c; // Complex Part
        };
      Complex* next;
    };
  };

然而,這個策略違反了用戶使用便利性的設(shè)計目標(biāo),因?yàn)樵诩蓛?nèi)存管理器的時候,我們希望在
原始數(shù)據(jù)結(jié)構(gòu)中進(jìn)行最小限度的更改。為了對此進(jìn)行改進(jìn),我們設(shè)計了一個新的 FreeStore 數(shù)據(jù)
結(jié)構(gòu),它是一個包裝數(shù)據(jù)結(jié)構(gòu),在保存為池中的一部分時用作指針,否則用作 Complex 對象。
清單 6 顯示了 FreeStore 對象的數(shù)據(jù)結(jié)構(gòu)。

清單 6. FreeStore 對象的數(shù)據(jù)結(jié)構(gòu)
struct FreeStore
{
FreeStore* next;
};

然后,該池成為 FreeStore 對象的鏈表,其中每一個都可以指向該池中的下一個元素,并且可以
用作一個 
Complex 對象。MemoryManager 類必須保存一個指針,以指向這個空閑池第一個元素的頭。
它應(yīng)該提供一些用于在需要時擴(kuò)展池大小的私有方法,以及在程序終止時清空內(nèi)存的方法。
清單 7 
包含了經(jīng)過修改的 
Complex 類的數(shù)據(jù)結(jié)構(gòu),從而提供 FreeStore 功能。


清單 7. 經(jīng)過修改的 Complex 類數(shù)據(jù)結(jié)構(gòu),其中提供了 FreeStore 功能
#include <sys/types.h> 

class MemoryManager: public IMemoryManager 
  { 
  struct FreeStore
    {
     FreeStore *next;
    }; 
  void expandPoolSize ();
  void cleanUp ();
  FreeStore* freeStoreHead;
  public:
    MemoryManager () { 
      freeStoreHead = 0;
      expandPoolSize ();
      }
    virtual ~MemoryManager () { 
      cleanUp ();
      }
    virtual void* allocate(size_t);
    virtual void   free(void*);
  };

MemoryManager gMemoryManager;

class Complex 
  {
  public:
    Complex (double a, double b): r (a), c (b) {}
    inline void* operator new(size_t);
    inline void   operator delete(void*);
  private:
    double r; // Real Part
    double c; // Complex Part
  };    

下面是用于內(nèi)存分配的偽代碼:

  1. 如果還沒有創(chuàng)建空閑存儲,那么創(chuàng)建空閑存儲,然后跳轉(zhuǎn)到步驟 3。
  2. 如果空閑存儲已經(jīng)耗盡,則創(chuàng)建一個新的空閑存儲。
  3. 返回空閑存儲的第一個元素,并且將空閑存儲的下一個元素標(biāo)記為空閑存儲的頭。

下面是用于內(nèi)存刪除的偽代碼:

  1. 讓刪除指針中的 next 域指向當(dāng)前空閑存儲的頭。
  2. 將刪除指針標(biāo)記為空閑存儲的頭。

清單 8 包含了 Complex 類的 new 和 delete 操作符的源代碼。清單 9 顯示了空閑存儲的擴(kuò)展和清理方法。
問題仍然存在。您能發(fā)現(xiàn)具體的問題嗎?


清單 8. 用于 Complex 類的自定義內(nèi)存分配或者釋放
inline void* MemoryManager::allocate(size_t size)
  {
  if (0 == freeStoreHead)
    expandPoolSize ();
  FreeStore* head = freeStoreHead;
  freeStoreHead = head->next;
  return head;
  }
inline void MemoryManager::free(void* deleted)
  {
  FreeStore* head = static_cast <FreeStore*> (deleted);
  head->next = freeStoreHead;
  freeStoreHead = head;
  }
void* Complex::operator new (size_t size) 
  {
  return gMemoryManager.allocate(size);
  }
void Complex::operator delete (void* pointerToDelete)
  {
  gMemoryManager.free(pointerToDelete);
  }      
空閑存儲的創(chuàng)建并不是那么簡單。關(guān)鍵在于理解相同的 FreeStore* 指針還可以用來表示 Complex 對象。
因此,單個 
FreeStore指針?biāo)璧拇笮”仨毷?nbsp;FreeStore* 或者 Complex 中較大的一個,如清單 9 中所示。
清單 9. 擴(kuò)展或者清空空閑存儲的代碼
#define POOLSIZE 32

void MemoryManager::expandPoolSize ()
  {
  size_t size = (sizeof(Complex) > sizeof(FreeStore*)) ?
    sizeof(Complex) : sizeof(FreeStore*);
  FreeStore* head = reinterpret_cast <FreeStore*> (new char[size]);
  freeStoreHead = head;

  for (int i = 0; i < POOLSIZE; i++) {
    head->next = reinterpret_cast <FreeStore*> (new char [size]);
    head = head->next;
    }

  head->next = 0;
  }

void MemoryManager::cleanUp()
  {
  FreeStore* nextPtr = freeStoreHead;
  for (; nextPtr; nextPtr = freeStoreHead) {
    freeStoreHead = freeStoreHead->next;
    delete [] nextPtr; // remember this was a char array
    }
  }      

大功告成!您已經(jīng)為 Complex 類創(chuàng)建了自定義的內(nèi)存管理器。記得基準(zhǔn)測試花費(fèi)了 3.5 秒。編譯并
運(yùn)行相同測試(不需要在主例程中對客戶端代碼進(jìn)行任何更改),現(xiàn)在顯示執(zhí)行該程序只需要花費(fèi)
0.67 秒!為什么存在這樣顯著的性能改進(jìn)呢?主要有兩個原因:

  • 在用戶和內(nèi)核代碼之間的切換數(shù)目明顯減少,因?yàn)樗ㄟ^將刪除的內(nèi)存收集回空閑存儲來重用這些內(nèi)存。
  • 這個內(nèi)存管理器是一個適合單線程執(zhí)行的分配器。我們并不打算在多線程環(huán)境中運(yùn)行這些代碼。

之前,我們曾問您是否發(fā)現(xiàn)了設(shè)計中的問題。這個問題就是,如果沒有刪除使用 new 創(chuàng)建的 Complex 對象,
那么在這種設(shè)計中就沒有辦法回收丟失的內(nèi)存。如果開發(fā)人員使用編譯器提供的 new 和 delete 全局操作符,
那么情況也是相同的。然而,內(nèi)存管理器不僅僅涉及性能改進(jìn),它還應(yīng)該能夠在設(shè)計中避免內(nèi)存泄漏。對于
使用 new 創(chuàng)建的 Complex 對象進(jìn)行顯式刪除的情況,cleanUp 例程僅僅將內(nèi)存返回給操作系統(tǒng)。這個問題只
能通過從操作系統(tǒng)中請求更大的內(nèi)存塊(在程序初始化期間)、并對它們進(jìn)行存儲來加以解決。使用 Complex 
的 new 操作符從這些塊中劃出部分內(nèi)存來響應(yīng)內(nèi)存的請求。cleanUp 例程在釋放這些塊時應(yīng)該將其作為一個整
體,而不是從這些內(nèi)存塊中創(chuàng)建的單個對象。

有趣的說明

仍然使用全局 new 和 delete 操作符創(chuàng)建空閑存儲的元素。然而,您也可以使用 malloc 和 free 的組合。在這
兩種情況下,執(zhí)行的平均時間基本相同。

 

位圖內(nèi)存管理器

可以在最初的固定大小內(nèi)存分配方案基礎(chǔ)上進(jìn)行有趣的改進(jìn),即位圖內(nèi)存管理器。在這個方案中,向操作系統(tǒng)較
大的塊進(jìn)行內(nèi)存請求,并且通過從這些塊劃出部分內(nèi)存來實(shí)現(xiàn)內(nèi)存的分配。通過將整個塊作為一個整體來完成釋
放操作,因而避免了任何潛在的泄漏。這種方法的另一個優(yōu)點(diǎn)是,它可以支持 new 和 delete 操作的數(shù)組版本。

在這種方法中,內(nèi)存管理器將請求較大的內(nèi)存塊。然后進(jìn)一步將這個塊劃分為較小的、且大小固定的內(nèi)存塊。在
這種情況下,每個內(nèi)存塊的大小都與 Complex 對象的大小相等。根據(jù)這些塊中內(nèi)存塊的數(shù)目,單獨(dú)地維護(hù)一個位
圖,以顯示每個塊的狀態(tài)(空閑、或者已分配),并且位的數(shù)目與內(nèi)存塊的數(shù)目相等。當(dāng)程序請求一個新的
 Complex 對象時,內(nèi)存管理器將根據(jù)位圖來獲得一個空閑塊。對于所有的空閑塊,將其對應(yīng)的位設(shè)置為 1。對于
已使用的塊,則將它們對應(yīng)的位設(shè)置為 0。要提供 new[ ] 和 delete[ ] 操作符,您需要維護(hù)一個輔助的數(shù)據(jù)結(jié)構(gòu),
以便在創(chuàng)建或者銷毀 Complex 數(shù)組對象時幫助跟蹤位圖中有多少位需要設(shè)置為 1 或者 0。

創(chuàng)建一個位圖內(nèi)存管理器

內(nèi)存管理器從操作系統(tǒng)中請求足夠大的內(nèi)存塊。從這個塊中劃分出部分內(nèi)存以進(jìn)行稍后的內(nèi)存分配。如果請求失敗,
那么內(nèi)存管理器將解決這個問題,并將合適的消息傳遞給用戶,表示它已經(jīng)退出。在這個階段中,位圖中的所有條
目都設(shè)置為 1。

如果內(nèi)存管理器耗盡了空閑的內(nèi)存塊,那么它將向操作系統(tǒng)進(jìn)一步請求較大的內(nèi)存塊。MemoryManager 數(shù)據(jù)結(jié)構(gòu)中的
每個位圖都可以用于增大對應(yīng)的內(nèi)存塊。然而,在調(diào)用刪除操作之后,用戶可以使用對應(yīng)的塊。因此,非連續(xù)的刪除
調(diào)用將導(dǎo)致所謂的分段的內(nèi)存,并且可以從這個內(nèi)存中提供適當(dāng)大小的塊。

清單 10 中給出了 MemoryManager 類的基本結(jié)構(gòu)。它包含 MemoryPoolList,后者保存了從操作系統(tǒng)中請求的內(nèi)存塊的
起始地址。對于每個塊,在 BitMapEntryList 中都存在一個相應(yīng)的條目。FreeMapEntries 是一種數(shù)據(jù)結(jié)構(gòu),可以用來
為 new 調(diào)用的非數(shù)組版本提高下一個可用空閑塊的搜索速度。


清單 10. MemoryManager 類的定義
class MemoryManager : public IMemoryManager 
  {
    std::vector<void*> MemoryPoolList;
    std::vector<BitMapEntry> BitMapEntryList;
    //the above two lists will maintain one-to-one correspondence and hence 
    //should be of same  size.
    std::set<BitMapEntry*> FreeMapEntries;
    std::map<void*, ArrayMemoryInfo> ArrayMemoryList;

  private:
    void* AllocateArrayMemory(size_t size);
    void* AllocateChunkAndInitBitMap();
    void SetBlockBit(void* object, bool flag);
    void SetMultipleBlockBits(ArrayMemoryInfo* info, bool flag);
  public: 
    MemoryManager( ) {}
    ~MemoryManager( ) {}
    void* allocate(size_t);
    void   free(void*);
    std::vector<void*> GetMemoryPoolList(); 
  };      

ArrayMemoryList 保存了為 Complex 對象數(shù)組所分配的內(nèi)存的相關(guān)信息。實(shí)際上,它是數(shù)組的起始地址到結(jié)構(gòu)的映射,
以便維護(hù)MemPoolListIndex、位圖中數(shù)組的 StartPosition 和數(shù)組的大小,如清單 11 中所示。

清單 11. ArrayInfo 結(jié)構(gòu)定義
typedef struct ArrayInfo
  {
  int   MemPoolListIndex;
  int   StartPosition;
  int   Size;
  }
ArrayMemoryInfo; 

為了簡化位圖的操作,將其作為存儲某些額外元數(shù)據(jù)信息的 BitMapEntry 對象來進(jìn)行維護(hù),如
清單 12 中所示。要在
位圖中設(shè)置一個或者多個位,可以使用 
SetBit 和 SetMultipleBits 應(yīng)用程序接口 (API)。FirstFreeBlock() API 可以
檢索位圖所指向的第一個空閑塊。

清單 12. BitMapEntry 類定義 
typedef struct BitMapEntry
  {
  int      Index;
  int      BlocksAvailable;
  int      BitMap[BIT_MAP_SIZE];
  public:
    BitMapEntry():BlocksAvailable(BIT_MAP_SIZE)
      {
      memset(BitMap,0xff,BIT_MAP_SIZE / sizeof(char)); 
      // initially all blocks are free and bit value 1 in the map denotes 
      // available block
      }
    void SetBit(int position, bool flag);
    void SetMultipleBits(int position, bool flag, int count);
    void SetRangeOfInt(int* element, int msb, int lsb, bool flag);
    Complex* FirstFreeBlock(size_t size);
    Complex* ComplexObjectAddress(int pos);
    void* Head();
  }
BitMapEntry; 


內(nèi)存管理器將初始化一個“n”位的數(shù)組(作為位圖),這樣一來,這個數(shù)組中的每個位對應(yīng)于所請求的內(nèi)存的一個
塊。將所有的條目都設(shè)置為 1。這項(xiàng)工作在 
BitMapEntry 的構(gòu)造函數(shù)中完成。

在對 Complex 類的 new 或者 malloc 的非數(shù)組版本進(jìn)行調(diào)用的過程中,內(nèi)存管理器將檢查 FreeMapEntries 數(shù)據(jù)結(jié)構(gòu)
中是否存在可用的塊。如果在其中找到了可用的塊,則返回這個塊,否則從操作系統(tǒng)中請求一個新的內(nèi)存塊,并設(shè)
置其對應(yīng)的 
BitMapEntry。從這個塊中劃出部分內(nèi)存塊返回給用戶,將其可用位設(shè)置為 0 以表示它不再空閑,并且
將指針返回給用戶,
清單 13 中顯示了相關(guān)的代碼。

清單 13. MemoryManager::allocate 定義

void* MemoryManager::allocate(size_t size)

  {

  if(size == sizeof(Complex))    // mon-array version

    {

    std::set<BitMapEntry*>::iterator freeMapI = FreeMapEntries.begin();

    if(freeMapI != FreeMapEntries.end())

      {

      BitMapEntry* mapEntry = *freeMapI;

      return mapEntry->FirstFreeBlock(size);

      }

    else

      {

      AllocateChunkAndInitBitMap();

      FreeMapEntries.insert(&(BitMapEntryList[BitMapEntryList.size() - 1]));

      return BitMapEntryList[BitMapEntryList.size() - 1].FirstFreeBlock(size);

      }

    }

  else  // array version

    {

    if(ArrayMemoryList.empty())

      {

      return AllocateArrayMemory(size);

      }

    else

      {

      std::map<void*, ArrayMemoryInfo>::iterator infoI =ArrayMemoryList.begin();

      std::map<void*, ArrayMemoryInfo>::iterator infoEndI =

        ArrayMemoryList.end();

      while(infoI != infoEndI)

        {

        ArrayMemoryInfo info = (*infoI).second;

        if(info.StartPosition != 0)  // search only in those mem blocks 

          continue;             // where allocation is done from first byte

        else

          {

          BitMapEntry* entry = &BitMapEntryList[info.MemPoolListIndex];

          if(entry->BlocksAvailable < (size / sizeof(Complex)))

            return AllocateArrayMemory(size);

          else

            {

            info.StartPosition = BIT_MAP_SIZE - entry->BlocksAvailable;

            info.Size = size / sizeof(Complex);

            Complex* baseAddress = static_cast<Complex*>(

              MemoryPoolList[info.MemPoolListIndex]) + info.StartPosition;

 

            ArrayMemoryList[baseAddress] = info;

            SetMultipleBlockBits(&info, false);

 

            return baseAddress;

            }

          }

        }

      }

    }

  }

 

清單 14 中給出了為單個塊設(shè)置或重置位的代碼。

清單 14. MemoryManager::SetBlockBit 定義

void MemoryManager::SetBlockBit(void* object, bool flag)
  {
  int i = BitMapEntryList.size() - 1;
  for (; i >= 0 ; i--)
    {
    BitMapEntry* bitMap = &BitMapEntryList[i];
    if((bitMap->Head <= object )&amp;&amp; 
      (&(static_cast<Complex*>(bitMap->Head))[BIT_MAP_SIZE-1] >= object))
      {
      int position = static_cast<Complex*>(object) - 
      static_cast<Complex*>(bitMap->Head);
      bitMap->SetBit(position, flag);
      flag ? bitMap->BlocksAvailable++ : bitMap->BlocksAvailable--;
      }
    }
  }    

 

清單 15 中給出了為多個塊設(shè)置或重置位的代碼。

清單 15. MemoryManager::SetMultipleBlockBit 定義

 

void MemoryManager::SetMultipleBlockBits(ArrayMemoryInfo* info, bool flag)
  {
  BitMapEntry* mapEntry = &BitMapEntryList[info->MemPoolListIndex];
  mapEntry->SetMultipleBits(info->StartPosition, flag, info->Size);
  }

 

對于數(shù)組版本而言,處理方式有一點(diǎn)不同。用于數(shù)組的內(nèi)存塊與那些用于單個對象內(nèi)存分配的
內(nèi)存塊分配自不同的內(nèi)存塊。這減少了在調(diào)用兩種類型的 new
 時搜索下一個空閑可用內(nèi)存的開銷。
內(nèi)存管理器將查找 ArrayMemoryList
 數(shù)據(jù)結(jié)構(gòu),以獲取為數(shù)組提供內(nèi)存的塊的有關(guān)信息。當(dāng)找到
時,它將獲得這個塊中的適當(dāng)部分,將其地址提供給用戶,并將相應(yīng)的位設(shè)置為 0。如果出于某
種原因而導(dǎo)致操作失敗,那么將從操作系統(tǒng)中請求一個新的塊,并從中劃分出部分內(nèi)存。兩種
new
調(diào)用的內(nèi)存塊作為 MemPoolList 的一部分存儲在 MemoryManager 類中

在調(diào)用 Complex 類型對象的指針的 delete、或者 free 的非數(shù)組版本的過程中,首先確定包含這
個指針的內(nèi)存塊(請參見清單 16
)。然后,根據(jù)這個塊的 BitMapEntry 將對應(yīng)的位重置為 1,
從而使其成為可用的塊。整個操作將花費(fèi) O(1) 時間。對于 delete []
,從 ArrayMemoryList 中
檢索相關(guān)信息,并且在已知起始位置和位圖大小的情況下,將這些位標(biāo)記為可用。

清單 16. MemoryManager::free 定義

void MemoryManager::free(void* object)
  {
  if(ArrayMemoryList.find(object) == ArrayMemoryList.end())
    SetBlockBit(object, true);         // simple block deletion 
  else
    {//memory block deletion
    ArrayMemoryInfo *info = &ArrayMemoryList[object];
    SetMultipleBlockBits(info, true);
    }
  }

下載部分中提供了位圖內(nèi)存管理器代碼的完整清單。它是這部分討論的內(nèi)存管理器的一個
工作示例,并且包括了我們在這個部分中討論的所有清單。

 

 繼承帶來的問題

派生類的大小很可能會與其基類有很大的不同。例如,可以考慮 Complex 類的一個派生變體,
稱為 
ComplexT,它用于跟蹤復(fù)數(shù)的值隨時間變化的情況,其中包含一個附加的 unsigned long 用來存儲時間。先前重寫的 new 或者 delete 操作符現(xiàn)在將無法工作,除非我們專門針對這個
派生變體再次定義它們。對于這個問題,有三種可能的解決方案

  • 使用 MemoryManager 為這兩個不同的類維護(hù)兩個不同的池。實(shí)際上,這意味著分配/釋
    放例程的兩個變體,以及用來存儲這兩個池的頭的兩個指針。這種方式可能奏效,但
    可伸縮性受到限制

  • 根據(jù)最大的派生類大小維護(hù)一個池。allocate 例程始終返回最大派生類的大小,無論
    是基類、派生類層次中的哪個對象在請求內(nèi)存。這樣可以很好地解決問題,但它并不是
    一個非常有價值的方法,因?yàn)槌绦虻膬?nèi)存空間占用將會增加

  • 為可變大小的分配創(chuàng)建通用的內(nèi)存管理器

基于空閑列表的內(nèi)存管理器

在一個典型的程序中,內(nèi)存塊的大多數(shù)請求都是特定大小的。這個內(nèi)存管理器將充分利用這個
特點(diǎn)。要實(shí)現(xiàn)這一點(diǎn),您需要維護(hù)一些列表,其中包含這種大小的空閑塊的地址。在技術(shù)行話
中,我們稱這些列表為
空閑列表。例如,可以考慮某個程序,它將提出 8、16、24、32、48
和 64 字節(jié)的內(nèi)存請求。出于這個目的,內(nèi)存管理器中包含六個不同的空閑列表,并請求從對
應(yīng)的列表提供特定大小的內(nèi)存。與位圖內(nèi)存管理器類似,在啟動時從操作系統(tǒng)中請求內(nèi)存,并
且內(nèi)存管理器在內(nèi)部將從操作系統(tǒng)請求的塊劃分到各個列表中。在用戶為某個對象請求內(nèi)存時,
比如“m”個字節(jié),那么會將對象的大小轉(zhuǎn)換為最接近的塊大小“n”,并且存在一個空閑列表用來
存儲大小為“n”的塊。

 有關(guān)保護(hù)字節(jié)(guard bytes)的簡單介紹

“n”字節(jié)的塊不僅存儲了對象,還存儲了一些元數(shù)據(jù)信息。每個塊的末尾附近都包含四個額外的
字節(jié),其中包含在堆內(nèi)存操作范例中稱為保護(hù)字節(jié)
 的內(nèi)容。通常,memcpy 和 memset 函數(shù)可以
訪問所分配內(nèi)存的界限之外的內(nèi)存字節(jié)。這將導(dǎo)致堆內(nèi)存的破壞。許多編譯器都為所分配的 mem
 塊添加了一些特殊字符,以充當(dāng)該塊的哨兵(或者保護(hù)字節(jié))。對于所分配內(nèi)存的這些塊所進(jìn)行
的任何訪問,都將檢查所分配的塊附近的特殊字符。如果找不到這些字符,那么將認(rèn)為在程序執(zhí)
行期間以非法的方式更改了它們的值,這暗示堆內(nèi)存不再處于有序的狀態(tài),并且因此受到了破壞。
通過這種機(jī)制,程序員可以捕獲一些所了解的內(nèi)存錯誤。在下面的示例中,這四個字節(jié)中的其中
兩個充當(dāng)了保護(hù)字節(jié),第三個字節(jié)用于存儲這個對象的大小,最后一個字節(jié)存儲了一個標(biāo)志,表
示這個對象是否可用、或者已經(jīng)被占用。

 創(chuàng)建一個空閑列表內(nèi)存管理器

如前所述,內(nèi)存管理器維護(hù)了一些指針的列表,這些指針指向可變大小的塊。它還對從操作系統(tǒng)
請求而來的內(nèi)存塊進(jìn)行維護(hù),并作為memoryPool。在 MemoryManager 調(diào)用其析構(gòu)函數(shù)時,這個池
可以用于釋放整個內(nèi)存塊。清單 17 顯示了該數(shù)據(jù)結(jié)構(gòu)。

清單 17. 基于空閑列表實(shí)現(xiàn)的 MemoryManager 類定義

class MemoryManager : public IMemoryManager 
  {
  private:
    VoidPtrList     Byte8PtrList;
    VoidPtrList     Byte16PtrList;
    VoidPtrList     Byte24PtrList;
    VoidPtrList     Byte32PtrList;
    VoidPtrList     Byte40PtrList;
    std::vector<void*>    MemoryPoolList;
 
    . . . . . . . . . //helper routines may go in here
 
  public: 
    MemoryManager( ) {}
    ~MemoryManager( ) {}
    void* allocate(size_t);
    void   free(void*);
  };    

 

我們的示例使用了大小為 16、28 和 32 字節(jié)的三個類,因而需要大小為 24、32和40
個字節(jié)的塊來保存它們的保護(hù)字節(jié)。因此,Byte24PtrListByte32PtrList 和
 Byte40PtrList 的使用最為頻繁,并且代碼也主要是和它們相關(guān)的。然而,設(shè)計相當(dāng)
靈活,也可以用于添加您所希望的其他大小的列表。

為這些類重載了操作符 new 和 delete,它們將調(diào)用委派給負(fù)責(zé)內(nèi)存分配和釋放的 
allocate 和 free 例程。下面將詳細(xì)地介紹這些例程。我們使用了大小為 1024 的
塊作為對象的初始計數(shù)。另外,將示例中使用的每個類的大小作為預(yù)定義的常數(shù)進(jìn)
行了存儲,如清單 18 中所示。

清單 18. 這個實(shí)現(xiàn)中所使用的預(yù)定義常數(shù)

 

const int JOB_SCHEDULER_SIZE = sizeof(JobScheduler);
const int COMPLEX_SIZE = sizeof(Complex);
const int COORDINATE_SIZE = sizeof(Coordinate);
const int POOL_SIZE = 1024; //number of elements in a single pool
//can be chosen based on application requirements 
const int MAX_BLOCK_SIZE = 36; //depending on the application it may change 
//In above case it came as 36

 

在 allocate 和 free 例程中使用了這些常數(shù)。

allocate 例程

對于給定大小的塊,allocate 例程將確定使用內(nèi)存管理器中的哪個列表。它將查找
必需的列表,以查看是否存在可用的塊。請注意,這個列表僅存儲了指向該塊的指
針,而不是塊本身(該塊是 MemoryPoolList 的一部分)。

如果空閑列表是空的,那么將從操作系統(tǒng)中請求附加的內(nèi)存塊,并由
InitialiseByte24ListInitialiseByte32List 和InitialiseByte40List 
例程將其組織為分區(qū)塊。

當(dāng)存在某個空閑塊地址可供使用時,將對應(yīng)的塊標(biāo)記為不可使用,設(shè)置其保護(hù)字節(jié),
并且返回該塊的地址。可以考慮如
清單 19 中所示的實(shí)現(xiàn)。

清單 19. MemoryManager::allocate 定義

 

void* MemoryManager::allocate(size_t size)
{
  void* base = 0;
  switch(size)
    {
    case JOB_SCHEDULER_SIZE :  
      {
      if(Byte32PtrList.empty())
        {
        base = new char [32 * POOL_SIZE];
        MemoryPoolList.push_back(base);
        InitialiseByte32List(base);
        }
      void* blockPtr =  Byte32PtrList.front();
      *((static_cast<char*>(blockPtr)) + 30) = 32; //size of block set
      *((static_cast<char*>(blockPtr)) + 31) = 0; //block is no longer free
      Byte32PtrList.pop_front();
      return blockPtr;
      }         
    case COORDINATE_SIZE :  
      {
      if(Byte40PtrList.empty())
        {
        base = new char [40 * POOL_SIZE];
        MemoryPoolList.push_back(base);
        InitialiseByte40List(base);
        }
      void* blockPtr =  Byte40PtrList.front();
      *((static_cast<char*>(blockPtr)) + 38) = 40; //size of block set
      *((static_cast<char*>(blockPtr)) + 39) = 0; //block is no longer free
      Byte40PtrList.pop_front();
      return blockPtr;
      }         
    case COMPLEX_SIZE : 
      {
      if(Byte24PtrList.empty())
        {
        base = new char [24 * POOL_SIZE];
        MemoryPoolList.push_back(base);
        InitialiseByte24List(base);
        }
      void* blockPtr =  Byte24PtrList.front();
      *((static_cast<char*>(blockPtr)) + 22) = 24; //size of block set
      *((static_cast<char*>(blockPtr)) + 23) = 0; //block is no longer free
      Byte24PtrList.pop_front();
      return blockPtr;
      }
    default : break;
    }
  return 0;
  }

 free 例程

 

free 例程接收塊的地址,并搜索位于塊末尾的、包含大小信息的字節(jié)(在保護(hù)字節(jié)中)。它是
塊中的倒數(shù)第二個字節(jié)。在獲得這個大小之后,將對象標(biāo)記為可用(將最后一個字節(jié)設(shè)置為 1),
并獲取對應(yīng)的列表。然后,將該對象加入
空閑 列表中,這也標(biāo)志著完成了刪除操作。可以考慮
清單 20 中的實(shí)現(xiàn)。

清單 20. MemoryManager::free 定義
void MemoryManager::free(void* object)

  {
  char* init = static_cast<char*>(object);
   while(1)
    {
    int count = 0;
    while(*init != static_cast<char>(0xde))  
          //this loop shall never iterate more than 
      {                 // MAX_BLOCK_SIZE times and hence is O(1)
      init++;
      if(count > MAX_BLOCK_SIZE)
        {
        printf ("runtime heap memory corruption near %d", object);
        exit(1);
        } 
      count++; 
      }
    if(*(++init) == static_cast<char>(0xad))  // we have hit the guard bytes
      break;  // from the outer while 
    }
  init++;
  int blockSize = static_cast<int>(*init);
  switch(blockSize)
    {
    case 24: Byte24PtrList.push_front(object); break;
    case 32: Byte32PtrList.push_front(object); break;
    case 40: Byte40PtrList.push_front(object); break;
    default: break;
    }
  init++;
  *init = 1; // set free/available byte   
  }  
下載部分中提供了空閑列表內(nèi)存管理器代碼的完整清單。它是空閑列表內(nèi)存管理器實(shí)現(xiàn)的一個
工作示例,并且包括我們在這個部分中討論的所有清單。

 

優(yōu)點(diǎn)和不足之處

這個設(shè)計有下面幾個優(yōu)點(diǎn):

  • 可以很好地處理可變大小的內(nèi)存分配

  • 設(shè)計靈活,并且可以通過確定每個軟件所需的列表大小來進(jìn)行擴(kuò)展

  • 添加保護(hù)字節(jié)的能力允許為對象存儲大量元數(shù)據(jù)信息。我們已經(jīng)將它
    用于確定運(yùn)行時堆內(nèi)存的破壞情況

雖然這個設(shè)計可以顯著地改善性能,但它仍然存在不足之處,即它需要大量的內(nèi)存。

 多線程內(nèi)存管理器

到目前為止,我們所創(chuàng)建的內(nèi)存管理器并沒有考慮到并發(fā)的環(huán)境。在多線程環(huán)境中,
可能會有多個線程同時嘗試進(jìn)行內(nèi)存的分配和釋放。這意味著,我們必須確保在內(nèi)
存管理器中進(jìn)行的分配和釋放操作都是具有原子性的。也就是說,在兩個線程同時
嘗試這些操作時,我們必須在這兩個線程之間提供一種互斥的機(jī)制。要確保分配和
釋放操作具有原子性,標(biāo)準(zhǔn)的方法是使用一種基于鎖的機(jī)制。當(dāng)一個線程調(diào)用這兩
種方法其中之一時,在它實(shí)際獲得對內(nèi)存的訪問或者釋放內(nèi)存之前,必須獲得一個
獨(dú)占鎖。如果該鎖由另一個線程所擁有,那么將阻塞當(dāng)前線程,直到它擁有這個鎖
為止。
清單 21 說明了用作鎖定機(jī)制的函數(shù)簽名。


清單 21. pthread mutex 方法

#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *mutex, 
const pthread_mutexattr_t *attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
 
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);   
 就本文而言,我們使用了標(biāo)準(zhǔn) Header pthread.h 中定義的 pthread mutexesGNU
編譯器在安裝時就提供了這個 Header,以便模擬鎖定或者解鎖機(jī)制。allocation 和 
free 例程中的代碼位于 pthread_mutex_lock 和 pthread_mutex_unlock 方法之間。
如果某個線程正在訪問內(nèi)存池時,又有另一個線程調(diào)用了這些例程其中之一,那么它
必須等待,直到該鎖可用為止。
清單 22 是該方法的經(jīng)過修改的代碼片段。

清單 22. 分配和釋放方法的并發(fā)版本

void* MemoryManager::allocate(size_t size) 
  {
  pthread_mutex_lock (&lock); 
  ... // usual memory allocation code
  pthread_mutex_unlock (&lock);
  }
 
void* MemoryManager::free(size_t size) 
  {
  pthread_mutex_lock (&lock); 
  ... // usual memory deallocation code
  pthread_mutex_unlock (&lock);
  }

 

現(xiàn)在,內(nèi)存管理器類需要包括一個 pthread_mutex_lock 對象。需要修改類構(gòu)造函數(shù),
以便調(diào)用 
pthread_mutex_init 初始化鎖,并且類析構(gòu)函數(shù)必須調(diào)用 pthread_mutex_destroy 
以銷毀 mutex 對象。清單 23 是 MemoryManager 類的修改之后的代碼。


清單 23. 經(jīng)過修改的 MemoryManager 類,以處理多線程的分配和釋放

class MemoryManager : public IMemoryManager 
  { 
  pthread_mutex_t lock;
  ... // usual MemoryManager code follows
  };
 
MemoryManager::MemoryManager () 
  {
  pthread_mutex_init (&lock, NULL);
  ... // usual constructor code 
  }
 
MemoryManager:: ~MemoryManager () 
  {
  ... // usual destructor code 
  pthread_mutex_destroy (&lock);
  } 

 

現(xiàn)在,運(yùn)行內(nèi)存管理器的多線程版本。在我們的測試中,與單線程版本相比,它的運(yùn)行要慢
得多,這說明了為什么需要提供專用的、自定義的內(nèi)存管理器。

總結(jié)

本教程解釋了下面的幾個概念:

  • 在用戶代碼中使用內(nèi)存管理器的需求。
  • 內(nèi)存管理器的設(shè)計需求。
  • 如何創(chuàng)建固定大小的分配器或者內(nèi)存管理器。
  • 如何創(chuàng)建可變大小的分配器。
  • 多線程環(huán)境中的內(nèi)存分配。

有關(guān)這個主題的更多信息,請參見參考資料部分。

下載

描述名字大小下載方法
Source files for bitmapped memory managerbitmapped_memory_manager.zip4KBHTTP
Source files for free-lists based memory managerfree_list_mem_mgr.zip3KBHTTP


參考資料

學(xué)習(xí)


from:
http://www.ibm.com/developerworks/cn/education/aix/au-memorymanager/index.html

 

 

posted on 2012-05-26 22:41 chatler 閱讀(2576) 評論(0)  編輯 收藏 引用 所屬分類: OS
<2025年11月>
2627282930311
2345678
9101112131415
16171819202122
23242526272829
30123456

常用鏈接

留言簿(10)

隨筆分類(307)

隨筆檔案(297)

algorithm

Books_Free_Online

C++

database

Linux

Linux shell

linux socket

misce

  • cloudward
  • 感覺這個博客還是不錯,雖然做的東西和我不大相關(guān),覺得看看還是有好處的

network

OSS

  • Google Android
  • Android is a software stack for mobile devices that includes an operating system, middleware and key applications. This early look at the Android SDK provides the tools and APIs necessary to begin developing applications on the Android platform using the Java programming language.
  • os161 file list

overall

搜索

  •  

最新評論

閱讀排行榜

評論排行榜

青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            欧美在线视频观看| 激情视频一区| 99国产精品久久久久久久久久| 欧美在线视频在线播放完整版免费观看 | 欧美激情在线| 一区二区在线视频| 欧美一区二区视频在线观看| 亚洲乱码国产乱码精品精98午夜| 久久亚洲二区| 狠狠久久亚洲欧美专区| 久久国产精品99精品国产| aa级大片欧美三级| 欧美精品久久一区二区| 亚洲黄色在线看| 免费美女久久99| 久久精品国产免费| 国产网站欧美日韩免费精品在线观看| 亚洲免费网址| 亚洲视频一二| 欧美日韩免费看| 99riav1国产精品视频| 欧美国产91| 免费观看在线综合| 在线免费一区三区| 美女精品视频一区| 久久免费视频观看| 激情自拍一区| 免费成人毛片| 乱中年女人伦av一区二区| 精品99视频| 麻豆av一区二区三区| 久久久91精品| 在线看片第一页欧美| 蜜臀久久久99精品久久久久久 | 99在线精品观看| 亚洲人成亚洲人成在线观看图片| 欧美成人综合网站| 亚洲美女区一区| 亚洲精品一区二区三区福利| 欧美精品在线视频| 亚洲午夜久久久| 宅男在线国产精品| 国产精品夜夜嗨| 久久精品女人| 久久精品伊人| 亚洲国产影院| 亚洲黄色在线| 欧美日韩影院| 西西裸体人体做爰大胆久久久| 亚洲女人天堂成人av在线| 国产麻豆精品theporn| 久久久国产精品一区| 久久久久久久久久久久久9999 | 国产精品日本一区二区| 久久av资源网| 久久久青草青青国产亚洲免观| 亚洲国产二区| 亚洲精品久久| 国产精品少妇自拍| 久久久免费精品| 免费成年人欧美视频| 一区二区久久久久久| 亚洲一区二区三区高清不卡| 国产三区精品| 欧美风情在线观看| 欧美日韩亚洲综合| 久久不见久久见免费视频1| 久久久午夜视频| 99在线观看免费视频精品观看| 亚洲一区二区精品| 影音国产精品| 日韩视频精品| 国内精品久久久久久久果冻传媒| 欧美激情四色| 国产精品久久毛片a| 免费亚洲一区| 欧美日韩中文字幕精品| 久久久久久欧美| 欧美激情精品久久久久| 欧美一级一区| 欧美成人精品在线| 欧美一级午夜免费电影| 欧美xart系列高清| 翔田千里一区二区| 美国成人直播| 性色av一区二区怡红| 久久一区二区三区四区| 亚洲综合精品| 美女国产一区| 欧美一二三区在线观看| 欧美成年人视频网站| 欧美一级黄色录像| 欧美精品一区二区三区蜜桃| 欧美在线视频二区| 欧美精品福利| 久久美女性网| 国产精品盗摄一区二区三区| 女仆av观看一区| 国产精品你懂得| 亚洲国产欧美在线| 国产午夜亚洲精品羞羞网站| 亚洲人成在线观看| 一区二区在线视频观看| aa级大片欧美| 亚洲国产一区二区视频| 性欧美大战久久久久久久免费观看 | 老司机精品视频一区二区三区| 午夜日韩激情| 欧美精品国产一区| 久久人人爽人人| 国产精品日韩欧美一区| 亚洲国产一区二区三区a毛片| 国产一区久久| 亚洲一区二区三区在线视频| 亚洲精品自在久久| 久久久噜噜噜久久人人看| 欧美一区二区三区电影在线观看| 欧美精品手机在线| 麻豆国产精品777777在线 | 亚洲第一毛片| 狠狠色伊人亚洲综合成人| 亚洲一区国产| 在线视频一区二区| 欧美高清不卡在线| 牛牛国产精品| 国产专区欧美精品| 亚洲女人天堂成人av在线| 亚洲性图久久| 欧美日产国产成人免费图片| 亚洲第一狼人社区| 在线日韩视频| 久久久成人精品| 久久久久五月天| 国产午夜一区二区三区| 亚洲欧美日韩精品久久久久| 亚洲欧美国产精品专区久久| 欧美私人网站| 日韩午夜在线播放| 一区二区三区|亚洲午夜| 欧美黄色一级视频| 91久久精品国产91久久| 亚洲精品国偷自产在线99热| 老鸭窝亚洲一区二区三区| 久久在线视频| 影音先锋日韩资源| 老**午夜毛片一区二区三区| 老司机午夜精品视频在线观看| 黄色成人免费观看| 久久都是精品| 噜噜噜久久亚洲精品国产品小说| 国产真实乱偷精品视频免| 久久成人免费日本黄色| 久久亚洲精品一区| 在线播放国产一区中文字幕剧情欧美 | 亚洲美洲欧洲综合国产一区| 欧美国产欧美亚州国产日韩mv天天看完整| 欧美国产成人精品| 亚洲精品日日夜夜| 欧美绝品在线观看成人午夜影视 | 久久久999国产| 麻豆精品国产91久久久久久| 在线看日韩av| 牛人盗摄一区二区三区视频| 亚洲国产美女精品久久久久∴| 99日韩精品| 欧美涩涩视频| 亚洲欧美国产高清va在线播| 久久精品国产v日韩v亚洲| 影视先锋久久| 欧美国产亚洲视频| 99视频精品全国免费| 性欧美videos另类喷潮| 国产一区二区中文字幕免费看| 久久综合久久久久88| 最新日韩中文字幕| 亚洲香蕉视频| 国产性天天综合网| 免费久久99精品国产自在现线| 亚洲欧洲日韩女同| 亚洲性图久久| 国产小视频国产精品| 免费成人黄色片| 99成人精品| 久久精品亚洲一区| 亚洲欧洲另类国产综合| 欧美日韩亚洲91| 午夜精品一区二区三区在线视| 美乳少妇欧美精品| 9色porny自拍视频一区二区| 国产精品夜夜夜一区二区三区尤| 久久精品亚洲一区二区| 亚洲国产视频一区| 午夜精品久久久久久久蜜桃app| 狠狠色狠狠色综合系列| 欧美激情中文不卡| 亚洲欧美国产毛片在线| 欧美激情五月| 欧美亚洲一区在线| 91久久综合亚洲鲁鲁五月天| 国产精品久久久久久久久动漫 |