当前位置: 首页 > news >正文

施磊C++ | 项目实战 | 手写移植SGI STL二级空间配置器内存池 项目源码

手写移植SGI STL二级空间配置器内存池 项目源码

笔者建议配合这两篇博客进行学习

侯捷 | C++ | 内存管理 | 学习笔记(二):第二章节 std::allocator-CSDN博客

施磊C++ | 项目实战 | SGI STL二级空间配置器源码剖析-CSDN博客

文章目录

  • 手写移植SGI STL二级空间配置器内存池 项目源码
    • 1.大致框架
    • 2.allocate函数
    • 3.refill函数
    • 4._S_chunk_alloc函数
    • 5.malloc_alloc::allocate函数
    • 6.deallocate函数
    • 7.reallocate
    • 8.完整版myallocator.h
    • 9.pch.h和pch.cpp
    • 10.测试

考虑的问题:多线程安全

空间配置器是容器使用的,而容器产生的对象是很有可能在多个线程中去操作的

1.大致框架

1.四个函数定义

2.重要的类型变量

3.两个辅助函数

4.静态成员函数初始化

#pragma once
#include<mutex>
//移植SGI STL二级空间配置器内存池 源码template<typename T>
class myallocator
{
public://开辟内存  __n是开辟的大小(多少字节)T* allocate(size_t __n);//释放内存  __n是释放的大小void deallocate(void* __p, size_t __n);//内存扩容或缩容void* reallocate(void* __P, size_t __old_sz, size_t __new_sz);//对象构造 定位new实现void construct(T *__p,const T&val){new (__p) T(val);}//对象析构void destroy(T* __p){__p->~T();}
private://自由链表是从8字节开始,以8字节为对齐方式,扩充到128字节//obj数组的第一个链表挂着的是8字节,第二个是16字节,一次类推到128字节enum { _ALIGN = 8 };//内存池最大的chunk块的大小enum { _MAX_BYTES = 128 };//自由链表的数量 16 = 128 / 8enum { _NFREELISTS = 16 };// 每一个内存chunk块(结点)的头信息union _Obj {union _Obj* _M_free_list_link;//保存下一个结点的地址char _M_client_data[1];  };// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为 0/*多线程环境下防止线程自己拷贝一个副本,会加一个volatile关键字,使得在数据段或者堆上的数据改变了每个线程都可以立马看到,防止多个线程看到的数据版本不一致*/static _Obj* volatile _S_free_list[_NFREELISTS];//s_free_list存储自由链表数组的地址,这是个数组名,大小就是上面的16//内存池基于freelist实现,需要考虑线程安全,主要做互斥操作 换成C++11的锁static std::mutex mtx;//已分配的内存chunk块的使用情况 初始化全为0//start~~end 分配给程序之后剩下的空闲内存起始和终止地址  heapsize 堆的大小,记录的是已经分配的内存大小 除以16算出来的就是追加量static char* _S_start_free;static char* _S_end_free;static size_t _S_heap_size;/*将 __bytes 上调至最邻近的 8 的倍数 _ALIGN==81-8 全都映射(对齐)到89-16 全都映射(对齐)到16*/static size_t _S_round_up(size_t __bytes){return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));}/*返回 __bytes 大小的chunk块位于 free-list 中的编号作用就是,如果申请的字节在1-8之间,就挂到内存块大小为8的那个链表下面如果申请的字节在9-16之间,就挂到内存块大小为16的那个链表下面*/static size_t _S_freelist_index(size_t __bytes) {return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);}//把分配好的chunk进行连接的static void* _S_refill(size_t __n);//主要负责分配自由链表,chunk块的static char* _S_chunk_alloc(size_t __size,int& __nobjs);
};//静态成员变量初始化
template <typename T>
char* myallocator<T>::_S_start_free = nullptr;
template <typename T>
char* myallocator<T>::_S_end_free = nullptr;
template <typename T>
size_t myallocator<T>::_S_heap_size = 0;template <typename T>
//这里的typename告诉编译器这里是一个类型定义
typename myallocator<T>::_Obj* volatile myallocator<T>::_S_free_list[_NFREELISTS]=
{nullptr,nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};template <typename T>
std::mutex myallocator<T>::mtx;

2.allocate函数

//开辟内存  __n是开辟的大小(多少字节)
T* allocate(size_t __n)
{//要传入的是字节数,而容器调用传进来的1,2,3这些数字是个数不是字节数,还要乘以T的大小才行__n = __n * sizeof(T);void* __ret = 0;//大于128字节,直接mallocif (__n > (size_t)_MAX_BYTES) {__ret = malloc_alloc::allocate(__n);}//小于128字节else {_Obj* volatile* __my_free_list= _S_free_list + _S_freelist_index(__n);//线程安全std::lock_guard<std::mutex> guard(mtx);_Obj* __result = *__my_free_list;//内存池有chunk块就去相应大小的链表下面拿,没有的话就看如何进行分配了if (__result == 0)//没有相应大小chunk块,下面解释如何分配__ret = _S_refill(_S_round_up(__n));else {//有相应大小chunk块,就从内存池里面拿出相应的块分配给程序*__my_free_list = __result->_M_free_list_link;__ret = __result;}}//返回我们从内存池申请的chunk块return (T*)__ret;
}

注意点:

1.vector容器传入的__n是对象个数,还要乘以对象类型T的大小才是我们要开辟的字节数

2.最后指针要强转

3.把线程安全换成c++11的互斥锁

3.refill函数

//把分配好的chunk进行连接的
static void* _S_refill(size_t __n)
{int __nobjs = 20;//chunk_alloc主要负责内存开辟char* __chunk = _S_chunk_alloc(__n, __nobjs);_Obj* volatile* __my_free_list;_Obj* __result;_Obj* __current_obj;_Obj* __next_obj;int __i;if (1 == __nobjs) return(__chunk);__my_free_list = _S_free_list + _S_freelist_index(__n);/* Build free list in chunk *///result和chunk都指向chunk_alloc分配的内存的首地址 chunk指针用来遍历chunk块,result指针指向开头的地方做一个保存__result = (_Obj*)__chunk;//把头指针的位置指向下一chunk块,因为当前的情形是已经要把当前自由链表头指针的位置分配出去了,而头指针的后面才是剩下的空闲的chunk块,再次分配是分配空闲的,分配出去的就没关系了*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);for (__i = 1; ; __i++) {__current_obj = __next_obj;//这行描述的就是遍历过程 先转成char*,这样每次加减都是以字节为单位,chunk指针每一次加8字节到下一个chunk块(不一定是8,这里只是说明,具体多少要看内存块的大小,反正就是到下一个chunk内存块了)__next_obj = (_Obj*)((char*)__next_obj + __n);if (__nobjs - 1 == __i) {__current_obj->_M_free_list_link = 0;break;}else {__current_obj->_M_free_list_link = __next_obj;}}//返回第一个空闲的chunk结点给容器使用return(__result);
}

4._S_chunk_alloc函数

	//主要负责分配自由链表,chunk块的static char* _S_chunk_alloc(size_t __size,int& __nobjs){char* __result;size_t __total_bytes = __size * __nobjs;size_t __bytes_left = _S_end_free - _S_start_free;if (__bytes_left >= __total_bytes) {__result = _S_start_free;_S_start_free += __total_bytes;return(__result);}else if (__bytes_left >= __size) {__nobjs = (int)(__bytes_left / __size);__total_bytes = __size * __nobjs;__result = _S_start_free;_S_start_free += __total_bytes;return(__result);}else {size_t __bytes_to_get =2 * __total_bytes + _S_round_up(_S_heap_size >> 4);// Try to make use of the left-over piece.if (__bytes_left > 0) {_Obj* volatile* __my_free_list =_S_free_list + _S_freelist_index(__bytes_left);((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;*__my_free_list = (_Obj*)_S_start_free;}_S_start_free = (char*)malloc(__bytes_to_get);if (nullptr == _S_start_free) {size_t __i;_Obj* volatile* __my_free_list;_Obj* __p;// Try to make do with what we have.  That can't// hurt.  We do not try smaller requests, since that tends// to result in disaster on multi-process machines.for (__i = __size;__i <= (size_t)_MAX_BYTES;__i += (size_t)_ALIGN) {__my_free_list = _S_free_list + _S_freelist_index(__i);__p = *__my_free_list;if (0 != __p) {*__my_free_list = __p->_M_free_list_link;_S_start_free = (char*)__p;_S_end_free = _S_start_free + __i;return(_S_chunk_alloc(__size, __nobjs));// Any leftover piece will eventually make it to the// right free list.}}_S_end_free = 0;	// In case of exception._S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);// This should either throw an// exception or remedy the situation.  Thus we assume it// succeeded.}_S_heap_size += __bytes_to_get;_S_end_free = _S_start_free + __bytes_to_get;return(_S_chunk_alloc(__size, __nobjs));}}

5.malloc_alloc::allocate函数

最后一行typedef __malloc_alloc_template<0> malloc_alloc;

这个类就是malloc_alloc

//封装了malloc和free函数,可以设置OOM释放内存的回调函数
template <int __inst>
class __malloc_alloc_template {private:static void* _S_oom_malloc(size_t);static void* _S_oom_realloc(void*, size_t);static void (*__malloc_alloc_oom_handler)();public:static void* allocate(size_t __n){void* __result = malloc(__n);if (0 == __result) __result = _S_oom_malloc(__n);return __result;}static void deallocate(void* __p, size_t /* __n */){free(__p);}static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz){void* __result = realloc(__p, __new_sz);if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);return __result;}static void (*__set_malloc_handler(void (*__f)()))(){void (*__old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = __f;return(__old);}};template <int __inst>
void (*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{void (*__my_malloc_handler)();void* __result;for (;;) {__my_malloc_handler = __malloc_alloc_oom_handler;if (0 == __my_malloc_handler) { throw std::bad_alloc(); }(*__my_malloc_handler)();__result = malloc(__n);if (__result) return(__result);}
}template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{void (*__my_malloc_handler)();void* __result;for (;;) {__my_malloc_handler = __malloc_alloc_oom_handler;if (0 == __my_malloc_handler) { throw std::bad_alloc(); }(*__my_malloc_handler)();__result = realloc(__p, __n);if (__result) return(__result);}
}typedef __malloc_alloc_template<0> malloc_alloc;

6.deallocate函数

//释放内存  __n是释放的大小
void deallocate(void* __p, size_t __n)
{//回收的过大 直接用freeif (__n > (size_t)_MAX_BYTES)malloc_alloc::deallocate(__p, __n);else {//定位到要回收的chunk块的地址_Obj* volatile* __my_free_list= _S_free_list + _S_freelist_index(__n);_Obj* __q = (_Obj*)__p;std::lock_guard<std::mutex> guard(mtx);//这里就是链表的头插法//要归还的块是要插入的块,要插入到头结点和头结点的next域指的节点(这个节点是第一个空闲块),而我们归还的这个块就接到头结点的next域,然后要回收的块再接着原来的第一个空闲块,成为新的第一个空闲块,就类比一下链表的头插法就行__q->_M_free_list_link = *__my_free_list;*__my_free_list = __q;// lock is released here 出作用域锁释放}
}

7.reallocate

//内存扩容或缩容
void* reallocate(void* __p, size_t __old_sz, size_t __new_sz)
{void* __result;size_t __copy_sz;//如果旧的和新的全都比128字节大,那说明这就不是从我内存池里面出去的,那就直接调用c的realloc函数if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {return(realloc(__p, __new_sz));}//如果新的和旧的对齐以后一样,那就不用扩容,直接返回 比如一个12,扩容到15,但是这两个的round_up都是16 那就没必要扩容了if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);//分配一块新的大小的内存空间__result = allocate(__new_sz);//比较大小,就选个比较小的__copy_sz = __new_sz > __old_sz ? __old_sz : __new_sz;//把内容copy到新开辟的内存空间memcpy(__result, __p, __copy_sz);//把旧的内存空间给释放掉deallocate(__p, __old_sz);//最后把新的内存地址返回return(__result);
}

8.完整版myallocator.h

#pragma once
#include<mutex>
#include<iostream>
//移植SGI STL二级空间配置器内存池 源码//封装了malloc和free函数,可以设置OOM释放内存的回调函数
template <int __inst>
class __malloc_alloc_template {private:static void* _S_oom_malloc(size_t);static void* _S_oom_realloc(void*, size_t);static void (*__malloc_alloc_oom_handler)();public:static void* allocate(size_t __n){void* __result = malloc(__n);if (0 == __result) __result = _S_oom_malloc(__n);return __result;}static void deallocate(void* __p, size_t /* __n */){free(__p);}static void* reallocate(void* __p, size_t /* old_sz */, size_t __new_sz){void* __result = realloc(__p, __new_sz);if (0 == __result) __result = _S_oom_realloc(__p, __new_sz);return __result;}static void (*__set_malloc_handler(void (*__f)()))(){void (*__old)() = __malloc_alloc_oom_handler;__malloc_alloc_oom_handler = __f;return(__old);}};template <int __inst>
void (*__malloc_alloc_template<__inst>::__malloc_alloc_oom_handler)() = 0;template <int __inst>
void*
__malloc_alloc_template<__inst>::_S_oom_malloc(size_t __n)
{void (*__my_malloc_handler)();void* __result;for (;;) {__my_malloc_handler = __malloc_alloc_oom_handler;if (0 == __my_malloc_handler) { throw std::bad_alloc(); }(*__my_malloc_handler)();__result = malloc(__n);if (__result) return(__result);}
}template <int __inst>
void* __malloc_alloc_template<__inst>::_S_oom_realloc(void* __p, size_t __n)
{void (*__my_malloc_handler)();void* __result;for (;;) {__my_malloc_handler = __malloc_alloc_oom_handler;if (0 == __my_malloc_handler) { throw std::bad_alloc(); }(*__my_malloc_handler)();__result = realloc(__p, __n);if (__result) return(__result);}
}typedef __malloc_alloc_template<0> malloc_alloc;template<typename T>
class myallocator
{
public://vector容器里面要用的东西,不写就会报错using value_type = T;//模仿写vector的allocator的构造和拷贝构造 不写也会报错的constexpr myallocator() noexcept{	// construct default allocator (do nothing)}constexpr myallocator(const myallocator&) noexcept = default;template<class _Other>constexpr myallocator(const myallocator<_Other>&) noexcept{	// construct from a related allocator (do nothing)}//开辟内存  __n是开辟的大小(多少字节)T* allocate(size_t __n){//要传入的是字节数,而容器调用传进来的1,2,3这些数字是个数不是字节数,还要乘以T的大小才行__n = __n * sizeof(T);void* __ret = 0;//大于128字节,直接mallocif (__n > (size_t)_MAX_BYTES) {__ret = malloc_alloc::allocate(__n);}//小于128字节else {_Obj* volatile* __my_free_list= _S_free_list + _S_freelist_index(__n);//线程安全std::lock_guard<std::mutex> guard(mtx);_Obj* __result = *__my_free_list;//内存池有chunk块就去相应大小的链表下面拿,没有的话就看如何进行分配了if (__result == 0)//没有相应大小chunk块,下面解释如何分配__ret = _S_refill(_S_round_up(__n));else {//有相应大小chunk块,就从内存池里面拿出相应的块分配给程序*__my_free_list = __result->_M_free_list_link;__ret = __result;}}//返回我们从内存池申请的chunk块return (T*)__ret;}//释放内存  __n是释放的大小void deallocate(void* __p, size_t __n){//回收的过大 直接用freeif (__n > (size_t)_MAX_BYTES)malloc_alloc::deallocate(__p, __n);else {//定位到要回收的chunk块的地址_Obj* volatile* __my_free_list= _S_free_list + _S_freelist_index(__n);_Obj* __q = (_Obj*)__p;std::lock_guard<std::mutex> guard(mtx);//这里就是链表的头插法//要归还的块是要插入的块,要插入到头结点和头结点的next域指的节点(这个节点是第一个空闲块),而我们归还的这个块就接到头结点的next域,然后要回收的块再接着原来的第一个空闲块,成为新的第一个空闲块,就类比一下链表的头插法就行__q->_M_free_list_link = *__my_free_list;*__my_free_list = __q;// lock is released here 出作用域锁释放}}//内存扩容或缩容void* reallocate(void* __p, size_t __old_sz, size_t __new_sz){void* __result;size_t __copy_sz;//如果旧的和新的全都比128字节大,那说明这就不是从我内存池里面出去的,那就直接调用c的realloc函数if (__old_sz > (size_t)_MAX_BYTES && __new_sz > (size_t)_MAX_BYTES) {return(realloc(__p, __new_sz));}//如果新的和旧的对齐以后一样,那就不用扩容,直接返回 比如一个12,扩容到15,但是这两个的round_up都是16 那就没必要扩容了if (_S_round_up(__old_sz) == _S_round_up(__new_sz)) return(__p);//分配一块新的大小的内存空间__result = allocate(__new_sz);//比较大小,就选个比较小的__copy_sz = __new_sz > __old_sz ? __old_sz : __new_sz;//把内容copy到新开辟的内存空间memcpy(__result, __p, __copy_sz);//把旧的内存空间给释放掉deallocate(__p, __old_sz);//最后把新的内存地址返回return(__result);}//对象构造 定位new实现void construct(T *__p,const T&val){new (__p) T(val);}//对象析构void destroy(T* __p){__p->~T();}
private://自由链表是从8字节开始,以8字节为对齐方式,扩充到128字节//obj数组的第一个链表挂着的是8字节,第二个是16字节,一次类推到128字节enum { _ALIGN = 8 };//内存池最大的chunk块的大小enum { _MAX_BYTES = 128 };//自由链表的数量 16 = 128 / 8enum { _NFREELISTS = 16 };// 每一个内存chunk块(结点)的头信息union _Obj {union _Obj* _M_free_list_link;//保存下一个结点的地址char _M_client_data[1];  };// 组织所有自由链表的数组,数组的每一个元素的类型是_Obj*,全部初始化为 0/*多线程环境下防止线程自己拷贝一个副本,会加一个volatile关键字,使得在数据段或者堆上的数据改变了每个线程都可以立马看到,防止多个线程看到的数据版本不一致*/static _Obj* volatile _S_free_list[_NFREELISTS];//s_free_list存储自由链表数组的地址,这是个数组名,大小就是上面的16//内存池基于freelist实现,需要考虑线程安全,主要做互斥操作static std::mutex mtx;//已分配的内存chunk块的使用情况 初始化全为0//start~~end 分配给程序之后剩下的空闲内存起始和终止地址  heapsize 堆的大小,记录的是已经分配的内存大小 除以16算出来的就是追加量static char* _S_start_free;static char* _S_end_free;static size_t _S_heap_size;/*将 __bytes 上调至最邻近的 8 的倍数 _ALIGN==81-8 全都映射(对齐)到89-16 全都映射(对齐)到16*/static size_t _S_round_up(size_t __bytes){return (((__bytes)+(size_t)_ALIGN - 1) & ~((size_t)_ALIGN - 1));}/*返回 __bytes 大小的chunk块位于 free-list 中的编号作用就是,如果申请的字节在1-8之间,就挂到内存块大小为8的那个链表下面如果申请的字节在9-16之间,就挂到内存块大小为16的那个链表下面*/static size_t _S_freelist_index(size_t __bytes) {return (((__bytes)+(size_t)_ALIGN - 1) / (size_t)_ALIGN - 1);}//把分配好的chunk进行连接的static void* _S_refill(size_t __n){int __nobjs = 20;//chunk_alloc主要负责内存开辟char* __chunk = _S_chunk_alloc(__n, __nobjs);_Obj* volatile* __my_free_list;_Obj* __result;_Obj* __current_obj;_Obj* __next_obj;int __i;if (1 == __nobjs) return(__chunk);__my_free_list = _S_free_list + _S_freelist_index(__n);/* Build free list in chunk *///result和chunk都指向chunk_alloc分配的内存的首地址 chunk指针用来遍历chunk块,result指针指向开头的地方做一个保存__result = (_Obj*)__chunk;//把头指针的位置指向下一chunk块,因为当前的情形是已经要把当前自由链表头指针的位置分配出去了,而头指针的后面才是剩下的空闲的chunk块,再次分配是分配空闲的,分配出去的就没关系了*__my_free_list = __next_obj = (_Obj*)(__chunk + __n);for (__i = 1; ; __i++) {__current_obj = __next_obj;//这行描述的就是遍历过程 先转成char*,这样每次加减都是以字节为单位,chunk指针每一次加8字节到下一个chunk块(不一定是8,这里只是说明,具体多少要看内存块的大小,反正就是到下一个chunk内存块了)__next_obj = (_Obj*)((char*)__next_obj + __n);if (__nobjs - 1 == __i) {__current_obj->_M_free_list_link = 0;break;}else {__current_obj->_M_free_list_link = __next_obj;}}//返回第一个空闲的chunk结点给容器使用return(__result);}//主要负责分配自由链表,chunk块的static char* _S_chunk_alloc(size_t __size,int& __nobjs){char* __result;size_t __total_bytes = __size * __nobjs;size_t __bytes_left = _S_end_free - _S_start_free;if (__bytes_left >= __total_bytes) {__result = _S_start_free;_S_start_free += __total_bytes;return(__result);}else if (__bytes_left >= __size) {__nobjs = (int)(__bytes_left / __size);__total_bytes = __size * __nobjs;__result = _S_start_free;_S_start_free += __total_bytes;return(__result);}else {size_t __bytes_to_get =2 * __total_bytes + _S_round_up(_S_heap_size >> 4);// Try to make use of the left-over piece.if (__bytes_left > 0) {_Obj* volatile* __my_free_list =_S_free_list + _S_freelist_index(__bytes_left);((_Obj*)_S_start_free)->_M_free_list_link = *__my_free_list;*__my_free_list = (_Obj*)_S_start_free;}_S_start_free = (char*)malloc(__bytes_to_get);if (nullptr == _S_start_free) {size_t __i;_Obj* volatile* __my_free_list;_Obj* __p;// Try to make do with what we have.  That can't// hurt.  We do not try smaller requests, since that tends// to result in disaster on multi-process machines.for (__i = __size;__i <= (size_t)_MAX_BYTES;__i += (size_t)_ALIGN) {__my_free_list = _S_free_list + _S_freelist_index(__i);__p = *__my_free_list;if (0 != __p) {*__my_free_list = __p->_M_free_list_link;_S_start_free = (char*)__p;_S_end_free = _S_start_free + __i;return(_S_chunk_alloc(__size, __nobjs));// Any leftover piece will eventually make it to the// right free list.}}_S_end_free = 0;	// In case of exception._S_start_free = (char*)malloc_alloc::allocate(__bytes_to_get);// This should either throw an// exception or remedy the situation.  Thus we assume it// succeeded.}_S_heap_size += __bytes_to_get;_S_end_free = _S_start_free + __bytes_to_get;return(_S_chunk_alloc(__size, __nobjs));}}};//静态成员变量初始化
template <typename T>
char* myallocator<T>::_S_start_free = nullptr;
template <typename T>
char* myallocator<T>::_S_end_free = nullptr;
template <typename T>
size_t myallocator<T>::_S_heap_size = 0;template <typename T>
//这里的typename告诉编译器这里是一个类型定义
typename myallocator<T>::_Obj* volatile myallocator<T>::_S_free_list[_NFREELISTS]=
{nullptr,nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr};template <typename T>
std::mutex myallocator<T>::mtx;

9.pch.h和pch.cpp

//.h
#pragma once
#ifndef PCH_H
#define PCH_H#endif
//.cpp
#include"pch.h"

10.测试

#include <iostream>
#include<vector>
#include"pch.h"
#include"myallocator.h"using namespace std;int main()
{vector<int, myallocator<int>> v;for (int i = 0; i < 100; i++)v.push_back(rand() % 1000);for (int val : v)cout << val << " ";cout << endl;return 0;
}

总结:

通过源码移植可以更加清楚内存池整个分配内存和释放的过程。

侯捷老师内存管理第二章和施磊老师的课程讲清楚了原理和流程。

施磊老师的手写移植内存池是进行实践。

相关文章:

施磊C++ | 项目实战 | 手写移植SGI STL二级空间配置器内存池 项目源码

手写移植SGI STL二级空间配置器内存池 项目源码 笔者建议配合这两篇博客进行学习 侯捷 | C | 内存管理 | 学习笔记&#xff08;二&#xff09;:第二章节 std::allocator-CSDN博客 施磊C | 项目实战 | SGI STL二级空间配置器源码剖析-CSDN博客 文章目录 手写移植SGI STL二级空…...

C++ | Leetcode C++题解之第507题完美数

题目&#xff1a; 题解&#xff1a; class Solution { public:bool checkPerfectNumber(int num) {if (num 1) {return false;}int sum 1;for (int d 2; d * d < num; d) {if (num % d 0) {sum d;if (d * d < num) {sum num / d;}}}return sum num;} };...

Git快速上手

概述 Git 是一个免费且开源的分布式版本控制系统&#xff0c;被广泛用于软件开发中的代码版本控制。通过使用 Git&#xff0c;开发者可以高效地追踪文件的变化历史&#xff0c;并支持多人协作开发。本教程将带你快速了解 Git 的基本概念和操作&#xff0c;帮助你开始使用 Git …...

宝塔如何部署Django项目(前后端分离篇)

一、环境安装 1、安装相关软件 点击软件商店&#xff0c;安装下面软件 一、宝塔部署前端 1、打包Vue项目 打开Vue3项目&#xff0c;输入下面打包命令&#xff0c;对Vue项目进行打包&#xff0c; npm run build 2、部署前端 点击宝塔的网站&#xff0c;在PHP项目里点击添加…...

JavaScript解析JSON对象及JSON字符串

1、问题概述&#xff1f; JavaScript解析JSON对象是常用功能之一。 此处我们要明确JSON对象和JSON字符串的区别&#xff1f;否则会给我们的解析带来困扰。 主要实现如下功能&#xff1a; 1、JavaScript解析JSON字符串和JSON对象? 2、JavaScript解析JSON数组? 3、JavaSc…...

Elasticsearch 构建实时数据可视化应用

Elasticsearch 构建实时数据可视化应用 Elasticsearch 构建实时数据可视化应用一、构建实时数据可视化应用的基本原则1. 数据采集2. 数据处理和清洗3. 数据存储和索引4. 数据可视化展示二、实时数据可视化应用数据存储和检索功能基于Elasticsearch构建实时数据搜索和过滤功能El…...

NVR批量管理软件/平台EasyNVR多个NVR同时管理:H.265与H.264编码优势和差异深度剖析

在数字化安防领域&#xff0c;视频监控系统正逐步成为各行各业不可或缺的一部分。随着技术的不断进步&#xff0c;传统的视频监控系统已经难以满足日益复杂和多变的监控需求。下面我们谈及NVR批量管理软件/平台EasyNVR平台H.265与H.264编码优势及差异。 一、EasyNVR视频汇聚平台…...

C/C++(六)多态

本文将介绍C的另一个基于继承的重要且复杂的机制&#xff0c;多态。 一、多态的概念 多态&#xff0c;就是多种形态&#xff0c;通俗来说就是不同的对象去完成某个行为&#xff0c;会产生不同的状态。 多态严格意义上分为静态多态与动态多态&#xff0c;我们平常说的多态一般…...

汽车及零配件企业海量文件数据如何管

汽车行业特点 汽车行业是工业企业皇冠上的一颗明珠&#xff0c;在国民经济中占据着举足轻重的地位。汽车行业具备技术密集、创新速度快、供应链复杂等特点&#xff0c;具体体现为&#xff1a; 技术密集&#xff1a;汽车行业是技术密集型行业&#xff0c;覆盖机械、电子、软件、…...

【AI学习】Mamba学习(十二):深入理解S4模型

#1024程序员节&#xff5c;征文# HiPPO的学习暂告一段落&#xff0c;按照“HiPPO->S4->Mamba 演化历程”&#xff0c;接着学习S4。 S4对应的论文&#xff1a;《Efficiently Modeling Long Sequences with Structured State Spaces》 文章链接&#xff1a;https://ar5iv…...

linux入门之必掌握知识点

#1024程序员节&#xff5c;征文# Linux基础 top命令详解 top命令是用来查看进程系统资源使用情况的工具&#xff0c;它可以动态的现实。 top命令执行后&#xff0c;按大写M可以按内存使用情况进行排序&#xff0c;大写P可以按CPU使用情况进行排序&#xff0c;大写H可以显示线…...

【Web.路由]——路由原理

这篇文章&#xff0c;我们来讲一讲什么是路由。 路由是 将用户请求地址映射为一个请求委托的过程&#xff0c;负责匹配传入的Http请求&#xff0c;然后将这些请求发送到应用的可执行终结点。 这里需要注意一个内容&#xff0c;发送到应用的可执行终结点。 路由的分类&#x…...

Spring Boot技术在中小企业设备管理中的应用

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…...

Lua表(Table)

软考鸭微信小程序 过软考,来软考鸭! 提供软考免费软考讲解视频、题库、软考试题、软考模考、软考查分、软考咨询等服务 Lua中的表&#xff08;table&#xff09;是一种核心数据结构&#xff0c;它既是数组也是字典&#xff0c;能够存储多种类型的数据&#xff0c;包括数字、字符…...

51单片机应用开发(进阶)---外部中断(按键+数码管显示0-F)

实现目标 1、巩固数码管、外部中断知识 2、具体实现&#xff1a;按键K4&#xff08;INT1&#xff09;每按一次&#xff0c;数码管从0依次递增显示至F&#xff0c;再按则循环显示。 一、共阳数码管 1.1 共阳数码管结构 1.2 共阳数码管码表 共阳不带小数点0-F段码为&#xff…...

怎么区分主谓宾I love you与主系表I am fine? 去掉宾语看句子完整性 主系表结构则侧重于描述主语的状态、特征或性质

主谓宾与主系表是英语句子结构中的两种基本类型&#xff0c;它们在关注点、动词分类以及句子完整性方面有所区别。具体分析如下&#xff1a; 关注点 主谓宾I love you&#xff1a;主谓宾结构主要关注动作和影响对象之间的关系[1]。这种结构强调的是动态和行为&#xff0c;通常描…...

私域流量运营的误区

私域流量运营是近年来营销领域的重要趋势&#xff0c;但在实际操作中&#xff0c;很多企业和个人容易陷入一些误区。以下是几个常见的私域流量运营误区及其解决方法&#xff1a; 1. 只关注流量&#xff0c;不重视内容 误区&#xff1a;许多运营者认为&#xff0c;只要吸引到足…...

VirtualBox虚拟机桥接模式固定ip详解

VirtualBox虚拟机桥接模式固定ip详解 VirtualBox 桥接设置Ubuntu 24.04使用固定IP问题记录 VirtualBox 桥接设置 为什么设置桥接模式&#xff1f;桥接模式可以实现物理机和虚拟机互相通信&#xff0c;虚拟机也可以访问互联网&#xff08;推荐万金油&#xff09;&#xff0c;物…...

面试问题基础记录24/10/24

面试问题基础记录24/10/24 问题一&#xff1a;LoRA是用在节省资源的场景下&#xff0c;那么LoRA具体是节省了内存带宽还是显存呢&#xff1f;问题二&#xff1a;假如用pytorch完成一个分类任务&#xff0c;那么具体的流程是怎么样的&#xff1f;问题三&#xff1a;详细介绍一下…...

中国区 Microsoft365主页链接请您参考:

Microsoft365主页链接请您参考&#xff1a; Redirecting PPAC链接请您参考&#xff1a; Power Platform admin center 关于Power Automate开启工单是在 https://portal.partner.microsoftonline.cn/Support/SupportOverview.aspx进行提交的。 对应所需对应管理员可以分配以下…...

Go encoding/json库

JSON在网络上广泛使用&#xff0c;是一种基于文本的数据传输方式。在本集中&#xff0c;我们将与 Daniel Marti 一起探索 Go 的 encoding/json 包和其他包。 本篇内容是根据2020年7月份[#141 {“encoding”:“json”}](https://changelog.com/gotime/141 “#141 {“encoding”…...

「实战应用」如何用图表控件LightningChart可视化天气数据?(二)

LightningChart.NET完全由GPU加速&#xff0c;并且性能经过优化&#xff0c;可用于实时显示海量数据-超过10亿个数据点。 LightningChart包括广泛的2D&#xff0c;高级3D&#xff0c;Polar&#xff0c;Smith&#xff0c;3D饼/甜甜圈&#xff0c;地理地图和GIS图表以及适用于科学…...

苹果瑕疵数据集苹果质量数据集YOLO格式VOC格式 深度学习 目标检测 数据集

一、数据集概述 数据集名称&#xff1a;2类苹果图像数据集 数据集包含两类样本&#xff1a;正常苹果和有瑕疵的苹果。正常苹果样本代表完好的苹果&#xff0c;而有瑕疵的苹果样本代表苹果表面可能存在的损伤、瑕疵或病害。每个样本都经过详细标记和描述&#xff0c;以便训练模…...

旧电脑安装Win11提示“这台电脑当前不满足windows11系统要求”,安装中断。怎么办?

前言 最近有很多小伙伴也获取了LTSC版本的Win11镜像&#xff0c;很大一部分小伙伴安装这个系统也是比较顺利的。 有顺利安装完成的&#xff0c;肯定也有安装不顺利的。这都是很正常的事情&#xff0c;毕竟这个镜像对电脑硬件要求还是挺高的。 有一部分小伙伴在安装Windows11 …...

深入理解QT多线程编程

文章目录 多线程用法QThread类QtConcurrent类QFutureSynchronizer类获取线程信息线程优先级获取线程状态线程局部存储使用线程池监听线程事件Qt是一个跨平台的应用程序开发框架,广泛应用于图形用户界面(GUI)开发。它提供了强大的多线程支持,允许开发者在应用程序中创建和管理…...

React四官方文档总结一UI与交互

代码下载 React官网已经都是函数式组件文档&#xff0c;没有类组件文档&#xff0c;但是还是支持类组件这种写法。 UI 描述 组件 组件 是 React 的核心概念之一&#xff0c;它们是构建用户界面&#xff08;UI&#xff09;的基础。React 允许你将标签、CSS 和 JavaScript 组…...

如何理解 HTTP 是无状态的,以及它与 Cookie 和 Session 之间的联系

文章目录 一、什么是 HTTP&#xff1f;无状态的含义 二、为什么 HTTP 是无状态的&#xff1f;三、Cookie 和 Session 的引入1. Cookie特点&#xff1a;示例&#xff1a; 2. Session特点&#xff1a;示例&#xff08;Java Servlet&#xff09;&#xff1a; 四、HTTP、Cookie 和 …...

OpenCV视觉分析之运动分析(2)背景减除类:BackgroundSubtractorKNN的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 K-最近邻&#xff08;K-nearest neighbours, KNN&#xff09;基于的背景/前景分割算法。 该类实现了如 319中所述的 K-最近邻背景减除。如果前景…...

android黑屏问题记录

近期出现了一个黑屏问题&#xff1a; 仪表显示&#xff0c;主副屏黑的 &#xff1a;原因背光开启太晚&#xff0c;导致拍照时候是黑的&#xff0c;太晚的原因是绘制进程出现异常导致重启延后了时间&#xff0c;绘制进程crash原因是hwc调用底层库卡住&#xff0c;需更新hwc对应的…...

SIP 业务举例之 Call Forwarding - No Answer(无应答呼叫转移)

目录 1. Call Forwarding - No Answer 简介 2. RFC5359 的 Call Forwarding - No Answer 信令流程 呼转开始 呼转完成 3. Call Forwording - No Answer 过程总结 博主wx:yuanlai45_csdn 博主qq:2777137742 想要 深入学习 5GC IMS 等通信知识(加入 51学通信),或者想要 …...

发展历程 网站建设/百度经验怎么赚钱

理论上Struts 2.0的Action无须实现任何接口或继承任何类型&#xff0c;但是&#xff0c;我们为了方便实现Action&#xff0c;大多数情况下都会继承 com.opensymphony.xwork2.ActionSupport类&#xff0c;并重载&#xff08;Override&#xff09;此类里的String execute()方法。…...

深圳开发app的公司有哪些/seo优化文章网站

数据库优化的讨论可以说是一个永恒的主题。资深的Oracle优化人员通常会要求提出性能问题的人对数据库做一个statspack&#xff0c;贴出数据库配置等等。还有的人认为要抓出执行最慢的语句来进行优化。但实际情况是&#xff0c;提出疑问的人很可能根本不懂执行计划&#xff0c;更…...

前端开发人员怎么做网站/1688的网站特色

var是否可以省略 一般情况下&#xff0c;是可以省略var的&#xff0c;但有两点值得注意&#xff1a; 1、var a1 与 a1 &#xff0c;这两条语句一般情况下作用是一样的。但是前者不能用delete删除。不过&#xff0c;绝大多数情况下&#xff0c;这种差异是可以忽略的。 2、在函数…...

百度怎么做关键词优化/seo是什么牌子

2019独角兽企业重金招聘Python工程师标准>>> 前言 灵感来源于前些天捡到钱了&#xff0c;就想着是时候给自己买辆车了&#xff0c;工作这么多年了应该对自己好一点&#xff0c;在网上搜索了一下看到这个车型。其实几年前是买过一辆的&#xff0c;但是不到一个月就被…...

杭州网站建设排名/优化最狠的手机优化软件

转载于:https://www.cnblogs.com/-flq/p/9449386.html...

网站介绍视频怎么做/如何注册一个平台

1.sed简介sed是文本处理命令&#xff0c;因为其强大的功能而可称之为一种数据流编辑器。sed 对文本的处理很强大&#xff0c;并且sed非常小&#xff0c;参数少&#xff0c;容易掌握&#xff0c;他的操作方式根awk 有点像。sed 一次处理一行内容。处理时&#xff0c;把当前处理的…...