亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

【譯】C++ 內存池 -- C++ Memory Pool .

系統(tǒng) 2546 0

轉自: http://blog.csdn.net/060/article/details/1326025

?

?

?

這是我翻譯的文章,來自 Code Project,

原文作者: DanDanger2000 .?

原文鏈接: http://www.codeproject.com/cpp/MemoryPool.asp

C++ 內存池

l ? 下載示例工程 – 105Kb

l ? 下載源代碼 – 17.3Kb

?

目錄
l? 引言
l? 它怎樣工作
l? 示例
l? 使用這些代碼
l? 好處
l? 關于代碼
l? ToDo
l? 歷史
?
引言
C/C++ 的內存分配 ( 通過 malloc new ) 可能需要花費很多時。
更糟糕的是,隨著時間的流逝,內存 (memory) 將形成碎片,所以一個應用程序的運行會越來越慢當它運行了很長時間和 / 或執(zhí)行了很多的內存分配 ( 釋放 ) 操作的時候。特別是,你經(jīng)常申請很小的一塊內存,堆 (heap) 會變成碎片的。
解決方案 : 你自己的內存池
一個 ( 可能的 ) 解決方法是內存池 (Memory Pool)
在啟動的時候,一個 內存池 ”(Memory Pool) 分配一塊很大的內存,并將會將這個大塊 (block) 分成較小的塊 (smaller chunks) 。每次你從內存池申請內存空間時,它會從先前已經(jīng)分配的塊 (chunks) 中得到, 而不是從操作系統(tǒng)。最大的優(yōu)勢在于:
l? 非常少 ( 幾沒有 ) 堆碎片
l? 比通常的內存申請 / 釋放 ( 比如通過 malloc , new ) 的方式快
另外,你可以得到以下好處:
l? 檢查任何一個指針是否在內存池里
l? 寫一個 堆轉儲 ( Heap-Dump )” 到你的硬盤 ( 對事后的調試非常有用 )
l? 某種 內存泄漏檢測 ( memory-leak detection )” :當你沒有釋放所有以前分配的內存時,內存池 (Memory Pool) 會拋出一個 斷言 ( assertion ).
它怎樣工作
讓我們看一看內存池 (Memory Pool) UML 模式圖:
這個模式圖只顯示了類 CMemoryPool 的一小部分,參看由 Doxygen 生成的文檔以得到詳細的類描述。
?
一個關于內存塊 (MemoryChunks) 的單詞
你應該從模式圖中看到, 內存池 (Memory Pool) 管理了一個指向結構體 SMemoryChunk ( m_ptrFirstChunk , m_ptrLastChunk , and m_ptrCursorChunk ) 的指針。這些塊 (chunks) 建立一個內存塊 (memory chunks) 的鏈表。各自指向鏈表中的下一個塊 (chunk) 。當從操作系統(tǒng)分配到一塊內存時,它將完全的被 SMemoryChunk s 管理。讓我們近一點看看一個塊 (chunk)

?

?

typedef? struct ?SMemoryChunk
...
{
??TByte?
* Data?;????????????? // ?The?actual?Data

??std::size_t?DataSize?;???? // ?Size?of?the?"Data"-Block
??std::size_t?UsedSize?;???? // ?actual?used?Size
?? bool ?IsAllocationChunk?;?? // ?true,?when?this?MemoryChunks
????????????????????????????
//
?Points?to?a?"Data"-Block
????????????????????????????
// ?which?can?be?deallocated?via?"free()"

??SMemoryChunk? * Next?;?????? // ?Pointer?to?the?Next?MemoryChunk
????????????????????????????
// ?in?the?List?(may?be?NULL)


}
?SmemoryChunk;
每個塊(chunk)持有一個指針,指針指向:
l? 一小塊內存 ( Data )
l? 從塊 (chunk) 開始的可用內存的總大小 ( DataSize )
l? 實際使用的大小 ( UsedSize )
l? 以及一個指向鏈表中下一個塊 (chunk) 的指針。
第一步:預申請內存 (pre-allocating the memory)
當你調用 CmemoryPool 的構造函數(shù),內存池 (Memory Pool) 將從操作系統(tǒng)申請它的第一塊 ( 大的 ) 內存塊 (memory-chunk)
/**/ /* Constructor
*****************
*/

CMemoryPool::CMemoryPool(
const ?std::size_t? & sInitialMemoryPoolSize,
?????????????????????????
const ?std::size_t? & sMemoryChunkSize,
?????????????????????????
const ?std::size_t? & sMinimalMemorySizeToAllocate,
?????????????????????????
bool ?bSetMemoryData)
... {
??m_ptrFirstChunk??
= ?NULL?;
??m_ptrLastChunk???
= ?NULL?;
??m_ptrCursorChunk?
= ?NULL?;

??m_sTotalMemoryPoolSize?
= ? 0 ?;
??m_sUsedMemoryPoolSize??
= ? 0 ?;
??m_sFreeMemoryPoolSize??
= ? 0 ?;

??m_sMemoryChunkSize???
= ?sMemoryChunkSize?;
??m_uiMemoryChunkCount?
= ? 0 ?;
??m_uiObjectCount??????
= ? 0 ?;

??m_bSetMemoryData???????????????
= ?bSetMemoryData?;
??m_sMinimalMemorySizeToAllocate?
= ?sMinimalMemorySizeToAllocate?;

??
// ?Allocate?the?Initial?amount?of?Memory?from?the?Operating-System...
??AllocateMemory(sInitialMemoryPoolSize)?;
}

類的所有成員通用的初始化在此完成, AllocateMemory 最終完成了從操作系統(tǒng)申請內存。
/**/ /* *****************
AllocateMemory
*****************
*/

bool ?CMemoryPool::AllocateMemory( const ?std::size_t? & sMemorySize)
... {
??std::size_t?sBestMemBlockSize?
= ?CalculateBestMemoryBlockSize(sMemorySize)?;
??
// ?allocate?from?Operating?System
??TByte? * ptrNewMemBlock? = ?(TByte? * )?malloc?(sBestMemBlockSize)?;
??...
那么,是如何管理數(shù)據(jù)的呢?
第二步:已分配內存的分割 (segmentation of allocated memory)
正如前面提到的, 內存池( Memory Pool ) 使用 SMemoryChunk s 管理所有數(shù)據(jù)。從OS申請完內存之后,我們的塊(chunks)和實際的內存塊(block)之間就不存在聯(lián)系:
Memory Pool after initial allocation
我們需要分配一個結構體 SmemoryChunk 的數(shù)組來管理內存塊:
?? // ?(AllocateMemory()continued)?:?
??...
??unsigned?
int ?uiNeededChunks? = ?CalculateNeededChunks(sMemorySize)?;
??
// ?allocate?Chunk-Array?to?Manage?the?Memory
??SMemoryChunk? * ptrNewChunks? = ?
????(SMemoryChunk?
* )?malloc?((uiNeededChunks? * ? sizeof (SMemoryChunk)))?;
??assert(((ptrNewMemBlock)?
&& ?(ptrNewChunks))?
???????????????????????????
&& ? " Error?:?System?ran?out?of?Memory " )?;
??...
CalculateNeededChunks() 負責計算為管理已經(jīng)得到的內存需要的塊(chunks)的數(shù)量。分配完塊(chunks)之后(通過 malloc ) ptrNewChunks 將指向一個 SmemoryChunk s 的數(shù)組。注意,數(shù)組里的塊 (chunks) 現(xiàn)在持有的是垃圾數(shù)據(jù),因為我們還沒有給 chunk-members 賦有用的數(shù)據(jù)。內存池的堆 (Memory Pool-"Heap"):
Memory Pool after SMemoryChunk allocation
還是那句話,數(shù)據(jù)塊 (data block) chunks 之間沒有聯(lián)系。但是, AllocateMemory() 會照顧它。 LinkChunksToData() 最后將把數(shù)據(jù)塊 (data block) chunks 聯(lián)系起來,并將為每個 chunk-member 賦一個可用的值。
// ?(AllocateMemory()continued)?:?
??...
??
// ?Associate?the?allocated?Memory-Block?with?the?Linked-List?of?MemoryChunks
?? return ?LinkChunksToData(ptrNewChunks,?uiNeededChunks,?ptrNewMemBlock)?;
讓我們看看 LinkChunksToData()
/**/ /* *****************
LinkChunksToData
*****************
*/

bool ?CMemoryPool::LinkChunksToData(SMemoryChunk? * ptrNewChunks,?
?????unsigned?
int ?uiChunkCount,?TByte? * ptrNewMemBlock)
... {
??SMemoryChunk?
* ptrNewChunk? = ?NULL?;
??unsigned?
int ?uiMemOffSet? = ? 0 ?;
??
bool ?bAllocationChunkAssigned? = ? false ?;
??
for (unsigned? int ?i? = ? 0 ;?i? < ?uiChunkCount;?i ++ )
??
... {
????
if ( ! m_ptrFirstChunk)
????
... {
??????m_ptrFirstChunk?
= ?SetChunkDefaults( & (ptrNewChunks[ 0 ]))?;
??????m_ptrLastChunk?
= ?m_ptrFirstChunk?;
??????m_ptrCursorChunk?
= ?m_ptrFirstChunk?;
????}

????
else
????
... {
??????ptrNewChunk?
= ?SetChunkDefaults( & (ptrNewChunks[i]))?;
??????m_ptrLastChunk
-> Next? = ?ptrNewChunk?;
??????m_ptrLastChunk?
= ?ptrNewChunk?;
????}

????
????uiMemOffSet?
= ?(i? * ?((unsigned? int )?m_sMemoryChunkSize))?;
????m_ptrLastChunk
-> Data? = ? & (ptrNewMemBlock[uiMemOffSet])?;

????
// ?The?first?Chunk?assigned?to?the?new?Memory-Block?will?be?
????
// ?a?"AllocationChunk".?This?means,?this?Chunks?stores?the
????
// ?"original"?Pointer?to?the?MemBlock?and?is?responsible?for
????
// ?"free()"ing?the?Memory?later....
???? if ( ! bAllocationChunkAssigned)
????
... {
??????m_ptrLastChunk
-> IsAllocationChunk? = ? true ?;
??????bAllocationChunkAssigned?
= ? true ?;
????}

??}

??
return ?RecalcChunkMemorySize(m_ptrFirstChunk,?m_uiMemoryChunkCount)?;
}

讓我們一步步地仔細看看這個重要的函數(shù):第一行檢查鏈表里是否已經(jīng)有可用的塊(chunks):
??...
??
if ( ! m_ptrFirstChunk)
??...
我們第一次給類的成員賦值:
??...
??m_ptrFirstChunk?
= ?SetChunkDefaults( & (ptrNewChunks[ 0 ]))?;
??m_ptrLastChunk?
= ?m_ptrFirstChunk?;
??m_ptrCursorChunk?
= ?m_ptrFirstChunk?;
??...
m_ptrFirstChunk 現(xiàn)在指向塊數(shù)組( chunks-array ) 第一個 塊,每一個塊嚴格的管理來自內存( memory block ) m_sMemoryChunkSize 個字節(jié)。一個 偏移量 ”(offset) ——這個值是可以計算的所以每個 (chunk) 能夠指向內存塊 ( memory block) 的特定部分。
?
??uiMemOffSet? = ?(i? * ?((unsigned? int )?m_sMemoryChunkSize))?;
??m_ptrLastChunk
-> Data? = ? & (ptrNewMemBlock[uiMemOffSet])?;?
另外,每個新的來自數(shù)組的 SmemoryChunk 將被追加到鏈表的最后 一個 元素(并且它自己將成為最后一個元素):
??...
??m_ptrLastChunk
-> Next? = ?ptrNewChunk?;
??m_ptrLastChunk?
= ?ptrNewChunk?;
??...
在接下來的 " for loop " 中,內存池 (memory pool) 將連續(xù)的給數(shù)組中的所有塊 (chunks) 賦一個可用的數(shù)據(jù)。
Memory and chunks linked together, pointing to valid data
最后,我們必須重新計算每個塊(chunk)能夠管理的總的內存大小。這是一個費時的,但是在新的內存追加到內存池時必須做的一件事。這個總的大小將被賦值給chunk的 DataSize 成員。
/**/ /* *****************
RecalcChunkMemorySize
*****************
*/

bool ?CMemoryPool::RecalcChunkMemorySize(SMemoryChunk? * ptrChunk,?
??????????????????unsigned?
int ?uiChunkCount)
... {
??unsigned?
int ?uiMemOffSet? = ? 0 ?;
??
for (unsigned? int ?i? = ? 0 ;?i? < ?uiChunkCount;?i ++ )
??
... {
????
if (ptrChunk)
????
... {
??????uiMemOffSet?
= ?(i? * ?((unsigned? int )?m_sMemoryChunkSize))?;
??????ptrChunk
-> DataSize? = ?
????????(((unsigned?
int )?m_sTotalMemoryPoolSize)? - ?uiMemOffSet)?;
??????ptrChunk?
= ?ptrChunk -> Next?;
????}

????
else
????
... {
?????assert(
false ? && ? " Error?:?ptrChunk?==?NULL " )?;
?????
return ? false ?;
????}

??}

??
return ? true ?;
}

RecalcChunkMemorySize 之后,每個chunk都知道它指向的空閑內存的大小。所以,將很容易確定一個chunk是否能夠持有一塊特定大小的內存:當 DataSize 成員大于 ( 或等于 ) 已經(jīng)申請的內存大小以及 DataSize 成員是 0 ,于是 chunk 有能力持有一塊內存。最后,內存分割完成了。為了不讓事情太抽象,我們假定內存池 (memory pool ) 包含600字節(jié),每個chunk持有100字節(jié)。
??
Memory segmentation finished. Each chunk manages exactly 100 bytes
第三步:從內存池申請內存 (requesting memory from the memory pool)
那么,如果用戶從內存池申請內存會發(fā)生什么?最初,內存池里的所有數(shù)據(jù)是空閑的可用的:
?
All memory blocks are available
我們看看 GetMemory :
/**/ /* *****************
GetMemory
*****************
*/

void ? * CMemoryPool::GetMemory( const ?std::size_t? & sMemorySize)
... {
??std::size_t?sBestMemBlockSize?
= ?CalculateBestMemoryBlockSize(sMemorySize)?;??
??SMemoryChunk?
* ptrChunk? = ?NULL?;
??
while ( ! ptrChunk)
??
... {
????
// ?Is?a?Chunks?available?to?hold?the?requested?amount?of?Memory??
????ptrChunk? = ?FindChunkSuitableToHoldMemory(sBestMemBlockSize)?;
????
if ?( ! ptrChunk)
????
... {
??????
// ?No?chunk?can?be?found
??????
// ?=>?Memory-Pool?is?to?small.?We?have?to?request?
??????
// ????more?Memory?from?the?Operating-System....
??????sBestMemBlockSize? = ?MaxValue(sBestMemBlockSize,?
????????CalculateBestMemoryBlockSize(m_sMinimalMemorySizeToAllocate))?;
??????AllocateMemory(sBestMemBlockSize)?;
????}

??}


??
// ?Finally,?a?suitable?Chunk?was?found.
??
// ?Adjust?the?Values?of?the?internal?"TotalSize"/"UsedSize"?Members?and?
??
// ?the?Values?of?the?MemoryChunk?itself.
??m_sUsedMemoryPoolSize? += ?sBestMemBlockSize?;
??m_sFreeMemoryPoolSize?
-= ?sBestMemBlockSize?;
??m_uiObjectCount
++ ?;
??SetMemoryChunkValues(ptrChunk,?sBestMemBlockSize)?;

??
// ?eventually,?return?the?Pointer?to?the?User
?? return ?(( void ? * )?ptrChunk -> Data)?;
}

當用戶從內存池中申請內存是,它將從鏈表搜索一個能夠持有被申請大小的chunk。那意味著:
l? 那個chunk的 DataSize 必須大于或等于被申請的內存的大小; ?
l? 那個chunk的 UsedSize 必須是 0
?
這由 FindChunkSuitableToHoldMemory ? 方法完成。如果它返回 NULL ,那么在內存池中沒有可用的內存。這將導致 AllocateMemory 的調用 ( 上面討論過 ) ,它將從 OS 申請更多的內存。如果返回值不是 NULL 一個可用的 chunk 被發(fā)現(xiàn)。 SetMemoryChunkValues 會調整 chunk 成員的值,并且最后 Data 指針被返回給用戶 ...
/**/ /* *****************
????SetMemoryChunkValues
????*****************
*/

void ?CMemoryPool::SetMemoryChunkValues(SMemoryChunk? * ptrChunk,?
?????
const ?std::size_t? & sMemBlockSize)
... {
??
if (ptrChunk)?
??
... {
????ptrChunk
-> UsedSize? = ?sMemBlockSize?;
??}

??...
????}
?
示例
假設,用戶從內存池申請 250 字節(jié):
?
?
Memory in use
如我們所見,每個內存塊(chunk)管理100字節(jié),所以在這里250字節(jié)不是很合適。發(fā)生了什么事?Well, GetMemory ? 從第一個chunk返回 Data 指針并把它的 UsedSize 設為300字節(jié),因為300字節(jié)是能夠被管理的內存的最小值并大于等于250。那些剩下的 (300 - 250 = 50) 字節(jié)被稱為內存池的 " memory overhead " 。這沒有看起來的那么壞,因為這些內存還可以使用 ( 它仍然在內存池里 )
FindChunkSuitableToHoldMemory 搜索可用 chunk 時,它僅僅從一個空的 chunk 跳到另一個空的 chunk 。那意味著,如果某個人申請另一塊內存 (memory-chunk) ,第四塊 ( 持有 300 字節(jié)的那個 ) 會成為下一個可用的 ("valid") chunk
?
Jump to next valid chunk
使用代碼
使用這些代碼是簡單的、直截了當?shù)模褐恍枰谀愕膽美锇? " CMemoryPool.h " ,并添加幾個相關的文件到你的 IDE/Makefile:
  • CMemoryPool.h
  • CMemoryPool.cpp
  • IMemoryBlock.h
  • SMemoryChunk.h
你只要創(chuàng)建一個 CmemoryPool 類的實例,你就可以從它里面申請內存。所有的內存池的配置在 CmemoryPool 類的構造函數(shù) ( 使用可選的參數(shù) ) 里完成。看一看頭文件 (" CMemoryPool.h ") Doxygen-doku 。所有的文件都有詳細的 (Doxygen-) 文檔。
應用舉例
MemPool::CMemoryPool? * g_ptrMemPool? = ? new ?MemPool::CMemoryPool()?;
char ? * ptrCharArray? = ?( char ? * )?g_ptrMemPool -> GetMemory( 100 )?;
...
g_ptrMemPool
-> FreeMemory(ptrCharArray,? 100 )?;
delete?g_ptrMemPool?;
好處
內存轉儲 (Memory dump)
你可以在任何時候通過 WriteMemoryDumpToFile( strFileName ) 寫一個 "memory dump" 到你的 HDD 。看看一個簡單的測試類的構造函數(shù) ( 使用內存池重載了 new delete 運算符 )
?
/**/ /* *****************
Constructor
*****************
*/

MyTestClass::MyTestClass()
... {
???m_cMyArray[
0 ]? = ? ' H ' ?;
???m_cMyArray[
1 ]? = ? ' e ' ?;
???m_cMyArray[
2 ]? = ? ' l ' ?;
???m_cMyArray[
3 ]? = ? ' l ' ?;
???m_cMyArray[
4 ]? = ? ' o ' ?;
???m_cMyArray[
5 ]? = ?NULL?;
???m_strMyString?
= ? " This?is?a?small?Test-String " ?;
???m_iMyInt?
= ? 12345 ?;

???m_fFloatValue?
= ? 23456.7890f ?;
???m_fDoubleValue?
= ? 6789.012345 ?;

???Next?
= ? this ?;
}

MyTestClass? * ptrTestClass? = ? new ?MyTestClass?;?
g_ptrMemPool
-> WriteMemoryDumpToFile( " MemoryDump.bin " )?;
看一看內存轉儲文件 (" MemoryDump.bin "):
如你所見,在內存轉儲里有 MyTestClass 類的所有成員的值。明顯的, "Hello" 字符串 ( m_cMyArray ) 在那里,以及整型數(shù) m_iMyInt (3930 0000 = 0x3039 = 12345 decimal) 等等。這對調式很有用。
速度測試
我在 Windows 平臺上做了幾個非常簡單的測試 ( 通過 timeGetTime() ) ,但是結果說明內存池大大提高了應用程序的速度。所有的測試在 Microsoft Visual Studio .NET 2003 debug 模式下 ( 測試計算機 : Intel Pentium IV Processor (32 bit), 1GB RAM, MS Windows XP Professional ).
// Array-test?(Memory?Pool):?
for (unsigned? int ?j? = ? 0 ;?j? < ?TestCount;?j ++ )
... {
????????
// ?ArraySize?=?1000
???? char ? * ptrArray? = ?( char ? * )?g_ptrMemPool -> GetMemory(ArraySize)??;
????g_ptrMemPool
-> FreeMemory(ptrArray,?ArraySize)?;
}

??
????
// Array-test?(Heap):
for (unsigned? int ?j? = ? 0 ;?j? < ?TestCount;?j ++ )
... {
????????
// ?ArraySize?=?1000
???? char ? * ptrArray? = ?( char ? * )?malloc(ArraySize)??;
????free(ptrArray)?;
???}

Results for the "array-test
?
??? //Class-Test for MemoryPool and Heap (overloaded new/delete)
? // Class-Test?for?MemoryPool?and?Heap?(overloaded?new/delete)?
for (unsigned? int ?j? = ? 0 ;?j? < ?TestCount;?j ++ )
... {
????MyTestClass?
* ptrTestClass? = ? new ?MyTestClass?;
????delete?ptrTestClass?;
}

?
Results for the "classes-test" (overloaded new / delete operators)
關于代碼
這些代碼在Windows和Linux平臺的下列編譯器測試通過:
  • Microsoft Visual C++ 6.0
  • Microsoft Visual C++ .NET 2003
  • MinGW (GCC) 3.4.4 (Windows)
  • GCC 4.0.X (Debian GNU Linux)
Microsoft Visual C++ 6.0 ( *.dsw , *.dsp ) Microsoft Visual C++ .NET 2003 ( *.sln , *.vcproj ) 的工程文件已經(jīng)包含在下載中。內存池僅用于 ANSI/ISO C++, 所以它應當在任何 OS 上的標準的 C++ 編譯器編譯。在 64 位處理器上應當沒有問題。
注意 :內存池不是線程安全的。
這個內存池還有許多改進的地方 ;-) ToDo 列表包括:
l? 對于大量的內存, memory-"overhead" 能夠足夠大。
l? 某些 CalculateNeededChunks 調用能夠通過從新設計某些方法而去掉
l? 更多的穩(wěn)定性測試 ( 特別是對于那些長期運行的應用程序 )
l? 做到線程安全。

【譯】C++ 內存池 -- C++ Memory Pool .


更多文章、技術交流、商務合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 国产免费播放一区二区三区 | 国产亚洲综合久久 | 久久久综合视频 | 亚洲高清中文字幕综合网 | 久久草在线视频国产一 | 在线播放ww | 五月婷婷激情综合网 | 欧美性生交xxxxx丝袜 | 四虎资源 | 日韩第一页在线 | 视频二区欧美 | 天天摸天天操天天爽 | 97se狠狠狠狠狼亚洲综合网 | 国产精品入口麻豆 | 国产成人精品久久综合 | 人人爱人人做 | 日韩一区二区三 | 狠狠狠狠狠狠 | 日韩美女一级毛片 | 99久久影院| 久久亚洲欧美成人精品 | 欧美色欧美亚洲高清在线视频 | 五月天国产精品 | 亚洲欧美日韩中文高清一 | 91尤物在线视频 | 久久免费在线观看 | 亚洲视频日韩 | 九九九网站 | 高清国产天干天干天干不卡顿 | 抱着cao才爽免费观看 | 四虎影视国产884a精品亚洲 | 91色综合综合热五月激情 | www色午夜| 猫咪视频成人永久免费观看 | 色视频欧美 | 精精国产www视频在线观看免费 | 伊人网伊人网 | 五月天中文在线 | 国产精品一区久久精品 | 精品视频在线观看一区二区三区 | 天天操天天射天天操 |