欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談C++空間配置器allocator

 更新時(shí)間:2021年06月09日 09:25:02   作者:lsgxeva  
在STL中,Memory Allocator處于最底層的位置,為一切的Container提供存儲(chǔ)服務(wù),是一切其他組件的基石。對(duì)于一般使用 STL 的用戶而言,Allocator是不可見的。本文將主要介紹C++空間配置器allocator

概述

在C++中,一個(gè)對(duì)象的內(nèi)存配置和釋放一般都包含兩個(gè)步驟,對(duì)于內(nèi)存的配置,首先是調(diào)用operator new來配置內(nèi)存,然后調(diào)用對(duì)象的類的構(gòu)造函數(shù)進(jìn)行初始化;而對(duì)于內(nèi)存釋放,首先是調(diào)用析構(gòu)函數(shù),然后調(diào)用 operator delete進(jìn)行釋放。 如以下代碼:

class Foo { ... };
Foo* pf = new Foo;
...
delete pf;

Allocator 的作用相當(dāng)于operator new 和operator delete的功能,只是它考慮得更加細(xì)致周全。SGI STL 中考慮到了內(nèi)存分配失敗的異常處理,內(nèi)置輕量級(jí)內(nèi)存池(主要用于處理小塊內(nèi)存的分配,應(yīng)對(duì)內(nèi)存碎片問題)實(shí)現(xiàn), 多線程中的內(nèi)存分配處理(主要是針對(duì)內(nèi)存池的互斥訪問)等,本文就主要分析 SGI STL 中在這三個(gè)方面是如何處理的。在介紹著三個(gè)方面之前,我們先來看看 Allocator的標(biāo)準(zhǔn)接口。

1. Allocator 的標(biāo)準(zhǔn)接口

在 SGI STL 中,Allocator的實(shí)現(xiàn)主要在文件alloc.h和stl_alloc.h文件中。根據(jù) STL 規(guī)范,Allocator 需提供如下的一些接口(見stl_alloc.h文件的第588行開始的class template allocator):

// 標(biāo)識(shí)數(shù)據(jù)類型的成員變量,關(guān)于中間的6個(gè)變量的涵義見后續(xù)文章(關(guān)于Traits編程技巧)
typedef alloc _Alloc;
typedef size_t     size_type;
typedef ptrdiff_t  difference_type;
typedef _Tp*       pointer;
typedef const _Tp* const_pointer;
typedef _Tp&       reference;
typedef const _Tp& const_reference;
typedef _Tp        value_type;
template <class _Tp1> struct rebind {
  typedef allocator<_Tp1> other;
}; // 一個(gè)嵌套的class template,僅包含一個(gè)成員變量 other
// 成員函數(shù)
allocator() __STL_NOTHROW {}  // 默認(rèn)構(gòu)造函數(shù),其中__STL_NOTHROW 在 stl_config.h中定義,要么為空,要么為 throw()
allocator(const allocator&) __STL_NOTHROW {}  // 拷貝構(gòu)造函數(shù)
template <class _Tp1> allocator(const allocator<_Tp1>&) __STL_NOTHROW {} // 泛化的拷貝構(gòu)造函數(shù)
~allocator() __STL_NOTHROW {} // 析構(gòu)函數(shù)
pointer address(reference __x) const { return &__x; } // 返回對(duì)象的地址
const_pointer address(const_reference __x) const { return &__x; }  // 返回const對(duì)象的地址
_Tp* allocate(size_type __n, const void* = 0) {
  return __n != 0 ? static_cast<_Tp*>(_Alloc::allocate(__n * sizeof(_Tp))) : 0; 
  // 配置空間,如果申請(qǐng)的空間塊數(shù)不為0,那么調(diào)用 _Alloc 也即 alloc 的 allocate 函數(shù)來分配內(nèi)存,
} //這里的 alloc 在 SGI STL 中默認(rèn)使用的是__default_alloc_template<__NODE_ALLOCATOR_THREADS, 0>這個(gè)實(shí)現(xiàn)(見第402行)
void deallocate(pointer __p, size_type __n) { _Alloc::deallocate(__p, __n * sizeof(_Tp)); } // 釋放空間
size_type max_size() const __STL_NOTHROW  // max_size() 函數(shù),返回可成功配置的最大值
    { return size_t(-1) / sizeof(_Tp); }  //這里沒看懂,這里的size_t(-1)是什么意思?
void construct(pointer __p, const _Tp& __val) { new(__p) _Tp(__val); } // 調(diào)用 new 來給新變量分配空間并賦值
void destroy(pointer __p) { __p->~_Tp(); } // 調(diào)用 _Tp 的析構(gòu)函數(shù)來釋放空間

在SGI STL中設(shè)計(jì)了如下幾個(gè)空間分配的 class template:

template <int __inst> class __malloc_alloc_template // Malloc-based allocator.  Typically slower than default alloc
typedef __malloc_alloc_template<0> malloc_alloc
template<class _Tp, class _Alloc> class simple_alloc
template <class _Alloc> class debug_alloc
template <bool threads, int inst> class __default_alloc_template // Default node allocator.
typedef __default_alloc_template<__NODE_ALLOCATOR_THREADS, 0> alloc
typedef __default_alloc_template<false, 0> single_client_alloc
template <class _Tp>class allocator
template<>class allocator<void>
template <class _Tp, class _Alloc>struct __allocator
template <class _Alloc>class __allocator<void, _Alloc>

其中simple_alloc,debug_alloc,allocator和__allocator的實(shí)現(xiàn)都比較簡(jiǎn)單,都是對(duì)其他適配器的一個(gè)簡(jiǎn)單封裝(因?yàn)閷?shí)際上還是調(diào)用其他配置器的方法,如_Alloc::allocate)。而真正內(nèi)容比較充實(shí)的是__malloc_alloc_template和__default_alloc_template這兩個(gè)配置器,這兩個(gè)配置器就是 SGI STL 配置器的精華所在。其中__malloc_alloc_template是SGI STL 的第一層配置器,只是對(duì)系統(tǒng)的malloc,realloc函數(shù)的一個(gè)簡(jiǎn)單封裝,并考慮到了分配失敗后的異常處理。而__default_alloc_template是SGI STL 的第二層配置器,在第一層配置器的基礎(chǔ)上還考慮了內(nèi)存碎片的問題,通過內(nèi)置一個(gè)輕量級(jí)的內(nèi)存池。下文將先介紹第一級(jí)配置器的異常處理機(jī)制,然后介紹第二級(jí)配置器的內(nèi)存池實(shí)現(xiàn),及在多線程環(huán)境下內(nèi)存池互斥訪問的機(jī)制。

2. SGI STL 內(nèi)存分配失敗的異常處理

內(nèi)存分配失敗一般是由于out-of-memory(oom),SGI STL 本身并不會(huì)去處理oom問題,而只是提供一個(gè) private 的函數(shù)指針成員和一個(gè) public 的設(shè)置該函數(shù)指針的方法,讓用戶來自定義異常處理邏輯:

private:
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
  static void (* __malloc_alloc_oom_handler)();  // 函數(shù)指針
#endif
public:
  static void (* __set_malloc_handler(void (*__f)()))() // 設(shè)置函數(shù)指針的public方法
  {
    void (* __old)() = __malloc_alloc_oom_handler;
    __malloc_alloc_oom_handler = __f;
    return(__old);
  }

如果用戶沒有調(diào)用該方法來設(shè)置異常處理函數(shù),那么就不做任何異常處理,僅僅是想標(biāo)準(zhǔn)錯(cuò)誤流輸出一句out of memory并退出程序(對(duì)于使用new和C++特性的情況而言,則是拋出一個(gè)std::bad_alloc()異常), 因?yàn)樵摵瘮?shù)指針的缺省值為0,此時(shí)對(duì)應(yīng)的異常處理是__THROW_BAD_ALLOC:

// line 152 ~ 155
#ifndef __STL_STATIC_TEMPLATE_MEMBER_BUG
template <int __inst>
void (* __malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;
#endif
// in _S_oom_malloc and _S_oom_realloc
__my_malloc_handler = __malloc_alloc_oom_handler;
if (0 == __my_malloc_handler) { __THROW_BAD_ALLOC; }
// in preprocess, line 41 ~ 50
#ifndef __THROW_BAD_ALLOC
#  if defined(__STL_NO_BAD_ALLOC) || !defined(__STL_USE_EXCEPTIONS)
#    include <stdio.h>
#    include <stdlib.h>
#    define __THROW_BAD_ALLOC fprintf(stderr, "out of memory\n"); exit(1)
#  else /* Standard conforming out-of-memory handling */
#    include <new>
#    define __THROW_BAD_ALLOC throw std::bad_alloc()
#  endif
#endif

SGI STL 內(nèi)存配置失敗的異常處理機(jī)制就是這樣子了,提供一個(gè)默認(rèn)的處理方法,也留有一個(gè)用戶自定義處理異常的接口。

3. SGI STL 內(nèi)置輕量級(jí)內(nèi)存池的實(shí)現(xiàn)

第一級(jí)配置器__malloc_alloc_template僅僅只是對(duì)malloc的一層封裝,沒有考慮可能出現(xiàn)的內(nèi)存碎片化問題。內(nèi)存碎片化問題在大量申請(qǐng)小塊內(nèi)存是可能非常嚴(yán)重,最終導(dǎo)致碎片化的空閑內(nèi)存無法充分利用。SGI 于是在第二級(jí)配置器__default_alloc_template中 內(nèi)置了一個(gè)輕量級(jí)的內(nèi)存池。 對(duì)于小內(nèi)存塊的申請(qǐng),從內(nèi)置的內(nèi)存池中分配。然后維護(hù)一些空閑內(nèi)存塊的鏈表(簡(jiǎn)記為空閑鏈表,free list),小塊內(nèi)存使用完后都回收到空閑鏈表中,這樣如果新來一個(gè)小內(nèi)存塊申請(qǐng),如果對(duì)應(yīng)的空閑鏈表不為空,就可以從空閑鏈表中分配空間給用戶。具體而言SGI默認(rèn)最大的小塊內(nèi)存大小為128bytes,并設(shè)置了128/8=16 個(gè)free list,每個(gè)list 分別維護(hù)大小為 8, 16, 24, …, 128bytes 的空間內(nèi)存塊(均為8的整數(shù)倍),如果用戶申請(qǐng)的空間大小不足8的倍數(shù),則向上取整。

SGI STL內(nèi)置內(nèi)存池的實(shí)現(xiàn)請(qǐng)看__default_alloc_template中被定義為 private 的這些成員變量和方法(去掉了部分預(yù)處理代碼和互斥處理的代碼):

private:
#if ! (defined(__SUNPRO_CC) || defined(__GNUC__))
    enum {_ALIGN = 8}; // 對(duì)齊大小
    enum {_MAX_BYTES = 128}; // 最大有內(nèi)置內(nèi)存池來分配的內(nèi)存大小
    enum {_NFREELISTS = 16}; // _MAX_BYTES/_ALIGN  // 空閑鏈表個(gè)數(shù)
# endif
  static size_t  _S_round_up(size_t __bytes) // 不是8的倍數(shù),向上取整
    { return (((__bytes) + (size_t) _ALIGN-1) & ~((size_t) _ALIGN - 1)); }
__PRIVATE:
  union _Obj { // 空閑鏈表的每個(gè)node的定義
        union _Obj* _M_free_list_link;
        char _M_client_data[1];   };
  static _Obj* __STL_VOLATILE _S_free_list[]; // 空閑鏈表數(shù)組
  static size_t _S_freelist_index(size_t __bytes) { // __bytes 對(duì)應(yīng)的free list的index
        return (((__bytes) + (size_t)_ALIGN-1)/(size_t)_ALIGN - 1);
  }
  static void* _S_refill(size_t __n); // 從內(nèi)存池中申請(qǐng)空間并構(gòu)建free list,然后從free list中分配空間給用戶
  static char* _S_chunk_alloc(size_t __size, int& __nobjs); // 從內(nèi)存池中分配空間
  static char* _S_start_free;  // 內(nèi)存池空閑部分的起始地址
  static char* _S_end_free; // 內(nèi)存池結(jié)束地址
  static size_t _S_heap_size; // 內(nèi)存池堆大小,主要用于配置內(nèi)存池的大小

函數(shù)_S_refill的邏輯是,先調(diào)用_S_chunk_alloc從內(nèi)存池中分配20塊小內(nèi)存(而不是用戶申請(qǐng)的1塊),將這20塊中的第一塊返回給用戶,而將剩下的19塊依次鏈接,構(gòu)建一個(gè)free list。這樣下次再申請(qǐng)同樣大小的內(nèi)存就不用再?gòu)膬?nèi)存池中取了。有了_S_refill,用戶申請(qǐng)空間時(shí),就不是直接從內(nèi)存池中取了,而是從 free list 中取。因此allocate和reallocate在相應(yīng)的free list為空時(shí)都只需直接調(diào)用_S_refill就行了。其中_S_refill和_S_chunk_alloc這兩個(gè)函數(shù)是該內(nèi)存池機(jī)制的核心。

__default_alloc_template對(duì)外提供的 public 的接口有allocate,deallocate和reallocate這三個(gè),其中涉及內(nèi)存分配的allocate和reallocate的邏輯思路是,首先看申請(qǐng)的size(已round up)對(duì)應(yīng)的free list是否為空,如果為空,則調(diào)用_S_refill來分配,否則直接從對(duì)應(yīng)的free list中分配。而deallocate的邏輯是直接將空間插入到相應(yīng)free list的最前面。

這里默認(rèn)是依次申請(qǐng)20塊,但如果內(nèi)存池空間不足以分配20塊時(shí),會(huì)盡量分配足夠多的塊,這些處理都在_S_chunk_alloc函數(shù)中。該函數(shù)的處理邏輯如下(源代碼這里就不貼了):

1) 能夠分配20塊

從內(nèi)存池分配20塊出來,改變_S_start_free的值,返回分配出來的內(nèi)存的起始地址

2) 不足以分配20塊,但至少能分配一塊

分配經(jīng)量多的塊數(shù),改變_S_start_free的值,返回分配出來的內(nèi)存的起始地址

3) 一塊也分配不了

首先計(jì)算新內(nèi)存池大小size_t __bytes_to_get = 2 * __total_bytes + _S_round_up(_S_heap_size >> 4)
將現(xiàn)在內(nèi)存池中剩余空間插入到適當(dāng)?shù)膄ree list中調(diào)用malloc來獲取一大片空間作為新的內(nèi)存池:

– 如果分配成功,則調(diào)整_S_end_free和_S_heap_size的值,并重新調(diào)用自身,從新的內(nèi)存池中給用戶分配空間; – 否則,分配失敗,考慮從比當(dāng)前申請(qǐng)的空間大的free list中分配空間,如果無法找不到這樣的非空free list,則調(diào)用第一級(jí)配置器的allocate,看oom機(jī)制能否解決問題。

SGI STL的輕量級(jí)內(nèi)存池的實(shí)現(xiàn)就是醬紫了,其實(shí)并不復(fù)雜。

4. SGI STL 內(nèi)存池在多線程下的互斥訪問

最后,我們來看看SGI STL中如何處理多線程下對(duì)內(nèi)存池互斥訪問的(實(shí)際上是對(duì)相應(yīng)的free list進(jìn)行互斥訪問,這里訪問是只需要對(duì)free list進(jìn)行修改的訪問操作)。在SGI的第二級(jí)配置器中與內(nèi)存池互斥訪問相關(guān)的就是_Lock這個(gè)類了,它僅僅只包含一個(gè)構(gòu)造函數(shù)和一個(gè)析構(gòu)函數(shù),但這兩個(gè)函數(shù)足夠了。在構(gòu)造函數(shù)中對(duì)內(nèi)存池加鎖,在析構(gòu)函數(shù)中對(duì)內(nèi)存池解鎖:

//// in __default_alloc_template
# ifdef __STL_THREADS
    static _STL_mutex_lock _S_node_allocator_lock; // 互斥鎖變量
# endif
class _Lock {
    public:
        _Lock() { __NODE_ALLOCATOR_LOCK; }
        ~_Lock() { __NODE_ALLOCATOR_UNLOCK; }
};
//// in preprocess
#ifdef __STL_THREADS
# include <stl_threads.h> // stl 的線程,只是對(duì)linux或windows線程的一個(gè)封裝
# define __NODE_ALLOCATOR_THREADS true
# ifdef __STL_SGI_THREADS
#   define __NODE_ALLOCATOR_LOCK if (threads && __us_rsthread_malloc) \
                { _S_node_allocator_lock._M_acquire_lock(); }  // 獲取鎖
#   define __NODE_ALLOCATOR_UNLOCK if (threads && __us_rsthread_malloc) \
                { _S_node_allocator_lock._M_release_lock(); }  // 釋放鎖
# else /* !__STL_SGI_THREADS */
#   define __NODE_ALLOCATOR_LOCK \
        { if (threads) _S_node_allocator_lock._M_acquire_lock(); }
#   define __NODE_ALLOCATOR_UNLOCK \
        { if (threads) _S_node_allocator_lock._M_release_lock(); }
# endif
#else /* !__STL_THREADS */
#   define __NODE_ALLOCATOR_LOCK
#   define __NODE_ALLOCATOR_UNLOCK
#   define __NODE_ALLOCATOR_THREADS false
#endif

由于在__default_alloc_template的對(duì)外接口中,只有allocate和deallocate中直接涉及到對(duì)free list進(jìn)行修改的操作,所以在這兩個(gè)函數(shù)中,在對(duì)free list進(jìn)行修改之前,都要實(shí)例化一個(gè)_Lock的對(duì)象__lock_instance,此時(shí)調(diào)用構(gòu)造函數(shù)進(jìn)行加鎖,當(dāng)函數(shù)結(jié)束時(shí),的對(duì)象__lock_instance自動(dòng)析構(gòu),釋放鎖。這樣,在多線程下,可以保證free list的一致性。

以上就是淺談C++空間配置器allocator的詳細(xì)內(nèi)容,更多關(guān)于C++空間配置器allocator的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 減小VC6編譯生成的exe文件的大小的方法

    減小VC6編譯生成的exe文件的大小的方法

    這篇文章主要介紹了減小VC6編譯生成的exe文件的大小的方法,需要的朋友可以參考下
    2015-01-01
  • C++讀寫(CSV,Yaml,二進(jìn)制)文件的方法詳解

    C++讀寫(CSV,Yaml,二進(jìn)制)文件的方法詳解

    為了處理文件,我們可以利用fstream庫(kù)。在這個(gè)庫(kù)里面有三種數(shù)據(jù)類型:ofstream,ifstream,fstream。本文將利用這個(gè)庫(kù)實(shí)現(xiàn)不同文件的讀寫操作,需要的可以參考一下
    2022-05-05
  • google c++程序測(cè)試框架googletest使用教程詳解

    google c++程序測(cè)試框架googletest使用教程詳解

    &#8203;GoogleTest 是 Google 的 C++ 測(cè)試和模擬框架,可以幫助程序員測(cè)試C++程序的結(jié)果預(yù)期,這篇文章主要介紹了google c++程序測(cè)試框架googletest使用教程,需要的朋友可以參考下
    2021-08-08
  • 一文詳解C++中的mutable關(guān)鍵字

    一文詳解C++中的mutable關(guān)鍵字

    在C++中mutable關(guān)鍵字正如字面意思所示,表示「可變的」之意,一般在以下兩種情況中使用較多,一是修飾類中的變量,用來突破const的限制,二是在Lambda表達(dá)式中使用,用來捕獲修改表達(dá)式之外的變量值,下面我們就針對(duì)這兩種使用場(chǎng)景逐個(gè)介紹
    2023-10-10
  • cmake添加一個(gè)庫(kù)的實(shí)現(xiàn)步驟

    cmake添加一個(gè)庫(kù)的實(shí)現(xiàn)步驟

    本文主要介紹了cmake添加一個(gè)庫(kù)的實(shí)現(xiàn)步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-06-06
  • C語(yǔ)言中sizeof和strlen的區(qū)別詳解

    C語(yǔ)言中sizeof和strlen的區(qū)別詳解

    這篇文章主要介紹了C語(yǔ)言中sizeof和strlen的區(qū)別,文中有通過代碼示例和相關(guān)例題給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-06-06
  • C++中圖片重命名實(shí)現(xiàn)代碼

    C++中圖片重命名實(shí)現(xiàn)代碼

    這篇文章主要介紹了C++中圖片重命名實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2017-05-05
  • C語(yǔ)言中pthread_exit()函數(shù)實(shí)現(xiàn)終止線程

    C語(yǔ)言中pthread_exit()函數(shù)實(shí)現(xiàn)終止線程

    本文主要介紹了C語(yǔ)言中pthread_exit()函數(shù)實(shí)現(xiàn)終止線程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-05-05
  • Visual?C++?6.0添加一個(gè)對(duì)話框的實(shí)現(xiàn)步驟

    Visual?C++?6.0添加一個(gè)對(duì)話框的實(shí)現(xiàn)步驟

    VC6.0是微軟公司推出的一款集成開發(fā)環(huán)境,本文主要介紹了Visual?C++?6.0添加一個(gè)對(duì)話框的實(shí)現(xiàn)步驟,具有一定的參考價(jià)值,感興趣的可以了解一下
    2024-06-06
  • vs code 配置python虛擬環(huán)境的方法

    vs code 配置python虛擬環(huán)境的方法

    這篇文章主要介紹了vs code 配置python虛擬環(huán)境的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03

最新評(píng)論