operator new() 和 malloc()
operator new()就是调用malloc来申请内存空间
所有的分配内存操作最终都将落在 malloc 上。malloc分配的实际内存要比申请的内存大,因为有附加信息,附加信息一般是固定的
allocators(VC6 BC5 G2.9 G4.9)
-
VC6版本
-
BC5版本
- G2.9版本
G2.9虽然定义了和其他版本分配器类似的<defalloc.h>,但是未使用它。G2.9使用了一种改进的allocator,即<stl_alloc.h>
allocator的目标:尽量减少malloc的次数、减少额外开销。<stl_alloc.h>能够减少额外开销:
减少额外开销:
额外开销来自于对该块空间的大小等的说明(Cookie),G2.9通过以下方法节省额外开销:
G2.9实际上的allocator是维护n个链表,每一个链表负责某一个特定大小的区块,如第1个链表1个字节的分配,第2个负责2个字节的分配,第3个负责3个字节的分配…没有时再去和操作系统申请并切割
同一个链表上的内存块可以省掉一些额外开销,如大小等信息就不必再加上
- G4.9版本(最新版本)
G4.9又摒弃了G2.9的设计,用了无特殊设计的VC6模式,如下
但G2.9版本也被保留了
allocator使用方法
int main() {
std::allocator<std::string> alloc; // 可以分配string的allocator对象
int n{5};
auto const p = alloc.allocate(n); // 分配n个未初始化的string
auto q = p;
alloc.construct(q++); // *q为空字符串
alloc.construct(q++, 10, 'o'); // *q为cccccccccc
alloc.construct(q++, "hello"); // *q为hello
std::cout << *p << std::endl; // 正确:使用string的输出运算符
//std::cout << *q << std::endl; // 错误:q指向未构造的内存
std::cout << p[0] << std::endl;
std::cout << p[1] << std::endl;
std::cout << p[2] << std::endl;
while (q != p) {
alloc.destroy(--q); // 释放我们真正构造的string
}
alloc.deallocate(p, n);
return 0;
}
allocator源码详解
源文件结构
这里面还有很多底层函数没找到实现在哪里,只能先看大概
1. 在QT5.8中,vector定义如下
template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
class vector : protected _Vector_base<_Tp, _Alloc>{}
template<typename _Tp, typename _Alloc>
struct _Vector_base{}
默认使用std::allocator分配器
2. allocator结构说明
(1)以qt5.8的allocator.h为例,最上一层定义了多个allocator,其中一个如下
(2)可以看到上面的allocator类中没有标准规定的deallocator等函数。但是它继承了基类**__allocator_base**,而该基类出现在c++allocator.h中,如下:
template<typename _Tp>
using __allocator_base = __gnu_cxx::new_allocator<_Tp>;
(3)进一步找到**<new_allocator.h>**文件,可以发现里面定义了allocator所需要的所有必须类,包括在deallocator在内。___gnu_cxx::new_allocator<_Tp>是大部分编译器实现allocator的最底层
allocator基类:new_allocator
参考:https://zhuanlan.zhihu.com/p/354191253
https://blog.csdn.net/mei_true/article/details/113951160
c++默认的内存分配器继承于___gnu_cxx::new_allocator<_Tp>,它有两个任务
- 分配对象内存、初始化对象
- 析构对象、释放对象内存
源码如下:
//使用std中size_t和ptrdiff_t定义,为了程序的可移值性适应不同平台
//size_t表示unsigned类型,ptrdiff_t保存两个指针相减的结果
using std::size_t;
using std::ptrdiff_t;
//1. 定义new_allocator模板
template<typename _Tp>
class new_allocator{
public:
typedef size_t size_type;//类型定义,__allocator_traits中介绍
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;//元素类型
//2. rebind,结构体模板(重要函数)
template<typename _Tp1>
struct rebind{
typedef new_allocator<_Tp1> other;
};
//3. 检测是否支持C++11标准;里面的变量含义将在后面内存学习中详细解答
#if __cplusplus >= 201103L
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2103. propagate_on_container_move_assignment
typedef std::true_type propagate_on_container_move_assignment;
#endif
//4. 无参构造,_GLIBCXX_USE_NOEXCEPT被define为noexcept,表示不会抛出异常
new_allocator() _GLIBCXX_USE_NOEXCEPT { }
//5. 拷贝构造函数
new_allocator(const new_allocator&) _GLIBCXX_USE_NOEXCEPT { }
//6. 构造函数模板,泛化的拷贝构造
template<typename _Tp1>
new_allocator(const new_allocator<_Tp1>&) _GLIBCXX_USE_NOEXCEPT { }
//7. 析构函数
~new_allocator() _GLIBCXX_USE_NOEXCEPT { }
//8. 定义address成员函数,refence是_Tp&类型,__addressof是当存在operator&的重载时, 依然能够获得变量的地址
//算式a.address(x)等同于&x
pointer address(reference __x) const _GLIBCXX_NOEXCEPT {
return std::__addressof(__x);
}
//9. 和上面的函数类似,只是采用const类型
const_pointer address(const_reference __x) const _GLIBCXX_NOEXCEPT {
return std::__addressof(__x);
}
// NB: __n is permitted to be 0.
// The C++ standard says nothing about what the return value is when __n == 0.
//10. 分配内存(重要函数)
pointer allocate(size_type __n, const void* = 0){
if (__n > this->max_size())
std::__throw_bad_alloc();
return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
}
// __p is not permitted to be a null pointer.
//11. 释放内存(重要函数)
void deallocate(pointer __p, size_type){
::operator delete(__p);
}
//12. 返回能分配的最大内存
//size_t对32位机来说是32位,对64位机来说是64位,size_t(-1)恰能表达其能代表的最大值(补码+1刚好所有位都为1)
//suze_t/sizeof(_Tp)就能表达在当前平台下所能返回的最大_Tp个数(理论上是4G,但实际上达不到)
size_type max_size() const _GLIBCXX_USE_NOEXCEPT {
return size_t(-1) / sizeof(_Tp);
}
//13. construct和destory
#if __cplusplus >= 201103L
template<typename _Up, typename... _Args> void construct(_Up* __p, _Args&&... __args) {
::new((void *)__p) _Up(std::forward<_Args>(__args)...);
}
template<typename _Up> void destroy(_Up* __p) {
__p->~_Up();
}
#else
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 402. wrong new expression in [some_] allocator::construct
void construct(pointer __p, const _Tp& __val) {
::new((void *)__p) _Tp(__val);
}
void destroy(pointer __p) {
__p->~_Tp();
}
#endif
};
下面将对部分重要成员进行详细说明
成员:rebind()
template<typename _Tp1>
struct rebind{
typedef new_allocator<_Tp1> other;
};
new_allocator是一个内存分配器,而rebind()的作用就是绑定不同数据类型的内存分配器。典型的就是list容器。
typedef typename _Alloc::template rebind<_List_node<_Tp> >::other _Node_alloc_type;
typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;
最简单的list包含一个 int data 和 struct node *next两个成员;当我们定义容器时将类型传给allocator,但就会导致allocator无法识别 struct node *next;
rebind()就是为了解决这种情况,它允许容器用一个分配器_Alloc再定义多个不同的分配器,以保证一致性。这样设置也使得new_allocator类的有参构造不被需要了
分别从vector和list的源码来看:
(1)vector
从调用先后依次为 c++/vector,c++/bits/stl_vector.h
class vector和它的基类 _Vector_base都在源文件<stl_vector.h>,文件只起到一个引入作用
template<typename _Tp, typename _Alloc>
struct _Vector_base{
typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template rebind<_Tp>::other _Tp_alloc_type;
typedef typename __gnu_cxx::__alloc_traits<_Tp_alloc_type>::pointer pointer;
...;
}
(2)list
从顶层到底层依次include的是 c++/list,c++/bits/stl_list.h,
class list和它的基类_List_base都在源文件<stl_list.h>之中
template<typename _Tp, typename _Alloc>
class _List_base{
protected:
typedef typename _Alloc::template rebind<_List_node<_Tp> >::other _Node_alloc_type;
typedef typename _Alloc::template rebind<_Tp>::other _Tp_alloc_type;
...;
}
- 可以看到,list和vector都执行了一次<_Tp>的rebind(),这相当于进行了有参构造。但list比vector多出了一次rebind(),这是因为list还需要一个节点内存分配器
- rebind主要服务于stl链式容器。vector的内存分配器是为整个vector服务的,当vector容量不够需要扩充时,它将重新用分配器申请一个vector空间,再复制过去。以int为例,分配器只需要分配int*n
- 而list不一样的是,同样定义int,但分配器不能给它分配int类型,因为每一个链表节点还包括前后指针,因此需要分配的是<_list_node>类型。rebind()可以使得 list 容器能像其他容器那样使用 listl 这样的便利方式
浅析:
list中还定义了一个list_node节点类,节点是一个<_ List _node< _Tp>>类型的数据结构,用 _Tp类型无法对其进行正确分配。
也可以为容器增加一个分配器参数,即两个分配器参数,但是为了确保两个分配器是一样的,更好还是使用rebind(),即避免 _Tp使用std::allocator分配器,而节点使用其他分配器如pool_allocator;(因为不同分配器分配空间大小可能不同)
rebind可以重新指定分配器分配空间的类型,实现了对不同类型使用同一种内存分配策略的要求。当容器需要对不同类型使用同一分配策略时,rebind的意义就体现出来。
成员:allocate()
配置内存空间,返回 __n (允许为0)个Tp类型大小的内存空间
pointer allocate(size_type __n, const void* = 0){
if (__n > this->max_size())
std::__throw_bad_alloc();
return static_cast<_Tp*>(::operator new(__n * sizeof(_Tp)));
}
- __n > this->max_size() 检查申请的大小是否超过当前进程最大可用内存,如果超过就抛出异常
- operator new是实际分配空间的操作,封装了malloc
- static_cast允许基本类型的转换和上行转换(即儿子指针转给父指针)
- const void* = 0:(尚未查到意义)
成员:deallocate()
void deallocate(pointer __p, size_type){
::operator delete(__p);
}
释放__p指向的内存空间
- __p不允许为空指针
- operator delete封装了free函数,用于释放内存
- 这里的size_type并没有被使用,因为free()不需要大小参数,但是其他分配器如boost的pool_allocator就使用了,为保证stl调用的一致性,使用了这一个参数
成员:construct()
allocate分配内存,construct初始化内存
template<typename _Up, typename... _Args> void construct(_Up* __p, _Args&&... __args) {
::new((void *)__p) _Up(std::forward<_Args>(__args)...);
}
construct的使用例子
allocator<string> alloc;//allocator对象,类型为string
auto const p = alloc.allocate(3);//为3个string分配空间,但未初始化
auto q = p;//q指向第一个未初始化的string
alloc.construct(q++);//为string初始化,第1个为空字符串
alloc.construct(q++,5,'a');//第2个为"aaaaa"
alloc.construct(q++,"hi");//第3个为"hi"
construct涉及到转发forward,std::forward将输入的参数原封不动(指左右值)地传递到下一个函数中(new中),这里可以当作forward不存在
成员:destory()
template<typename _Up> void destroy(_Up* __p) {
__p->~_Up();
}
调用_Up的析构函数,释放其空间
class allocator
- 继承了new_allocator
- 有关于分配的重载,但后面的几个函数暂时不是很清楚
#ifndef _ALLOCATOR_H
#define _ALLOCATOR_H 1
#include <bits/c++allocator.h> // Define the base class to std::allocator.
#include <bits/memoryfwd.h>
#if __cplusplus >= 201103L
#include <type_traits>
#endif
namespace std _GLIBCXX_VISIBILITY(default) {
_GLIBCXX_BEGIN_NAMESPACE_VERSION
//没有继承父类的allocator
template<>
class allocator<void> {
public:
typedef size_t size_type;
typedef ptrdiff_t difference_type;
typedef void* pointer;
typedef const void* const_pointer;
typedef void value_type;
//定义rebind
template<typename _Tp1>
struct rebind {
typedef allocator<_Tp1> other;
};
#if __cplusplus >= 201103L
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2103. std::allocator propagate_on_container_move_assignment
typedef true_type propagate_on_container_move_assignment;
#endif
};
//继承父类的allocator,__allocator_base实际上就是new_allocator
template<typename _Tp>
class allocator: public __allocator_base<_Tp> {
public:
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;
//重写父类的rebind
template<typename _Tp1>
struct rebind {
typedef allocator<_Tp1> other;
};
#if __cplusplus >= 201103L
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// 2103. std::allocator propagate_on_container_move_assignment
typedef true_type propagate_on_container_move_assignment;
#endif
//无参构造
allocator() throw() { }
//拷贝构造
allocator(const allocator& __a) throw() : __allocator_base<_Tp>(__a) { }
template<typename _Tp1>
allocator(const allocator<_Tp1>&) throw() { }
~allocator() throw() { }
// Inherit everything else.
// 从new_allocator那里继承allocate, deallocate, construct, destory等重要函数
};
//对==和!=的重载,这里的4个重载函数都表达的是:只要是用std::allocator实例化的对象,就一定相同,无论类型是什么
template<typename _T1, typename _T2>
inline bool operator==(const allocator<_T1>&, const allocator<_T2>&) _GLIBCXX_USE_NOEXCEPT {
return true;
}
template<typename _Tp>
inline bool operator==(const allocator<_Tp>&, const allocator<_Tp>&) _GLIBCXX_USE_NOEXCEPT {
return true;
}
template<typename _T1, typename _T2>
inline bool operator!=(const allocator<_T1>&, const allocator<_T2>&) _GLIBCXX_USE_NOEXCEPT {
return false;
}
template<typename _Tp>
inline bool operator!=(const allocator<_Tp>&, const allocator<_Tp>&) _GLIBCXX_USE_NOEXCEPT {
return false;
}
/// @} group allocator
// Inhibit implicit instantiations for required instantiations,
// which are defined via explicit instantiations elsewhere.
#if _GLIBCXX_EXTERN_TEMPLATE
extern template class allocator<char>;
extern template class allocator<wchar_t>;
#endif
// Undefine.
#undef __allocator_base
// To implement Option 3 of DR 431.
template<typename _Alloc, bool = __is_empty(_Alloc)>
struct __alloc_swap {
static void _S_do_it(_Alloc&, _Alloc&) _GLIBCXX_NOEXCEPT { }
};
//如果分配器不一样,就进行交换(应用?)
template<typename _Alloc>
struct __alloc_swap<_Alloc, false> {
static void _S_do_it(_Alloc& __one, _Alloc& __two) _GLIBCXX_NOEXCEPT {
// Precondition: swappable allocators.
if (__one != __two)
swap(__one, __two);
}
};
// neq表示不等的意思
// Optimize for stateless allocators.
template<typename _Alloc, bool = __is_empty(_Alloc)>
struct __alloc_neq {
static bool _S_do_it(const _Alloc&, const _Alloc&) {
return false;
}
};
template<typename _Alloc>
struct __alloc_neq<_Alloc, false> {
static bool _S_do_it(const _Alloc& __one, const _Alloc& __two) {
return __one != __two;
}
};
#if __cplusplus >= 201103L
template<typename _Tp, bool = __or_<is_copy_constructible<typename _Tp::value_type>,
is_nothrow_move_constructible<typename _Tp::value_type>>::value>
struct __shrink_to_fit_aux {
static bool _S_do_it(_Tp&) noexcept {
return false;
}
};
template<typename _Tp>
struct __shrink_to_fit_aux<_Tp, true> {
static bool _S_do_it(_Tp& __c) noexcept {
#if __cpp_exceptions
try {
_Tp(__make_move_if_noexcept_iterator(__c.begin()),
__make_move_if_noexcept_iterator(__c.end()),
__c.get_allocator()).swap(__c);
return true;
} catch(...) { return false; }
#else
return false;
#endif
}
};
#endif
_GLIBCXX_END_NAMESPACE_VERSION
} // namespace std
#endif