示例:new/delete 一个对象 代码 1 2 3 4 5 int main (int argc, char ** argv) { std::string* str = new std::string (); delete str; return 0 ; }
汇编 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 0x108852450 <+0 >: pushq %rbp 0x108852451 <+1 >: movq %rsp, %rbp 0x108852454 <+4 >: subq $0 x30, %rsp 0x108852458 <+8 >: movl $0 x0, -0x4 (%rbp) 0x10885245f <+15 >: movl %edi, -0x8 (%rbp) 0x108852462 <+18 >: movq %rsi, -0x10 (%rbp) 0x108852466 <+22 >: movl $0 x18, %edi 0x10885246b <+27 >: callq 0x1088c26c6 0x108852470 <+32 >: movq %rax, %rdi 0x108852473 <+35 >: movq %rax, -0x20 (%rbp) 0x108852477 <+39 >: callq 0x1088524c0 0x10885247c <+44 >: movq -0x20 (%rbp), %rax 0x108852480 <+48 >: movq %rax, -0x18 (%rbp) 0x108852484 <+52 >: movq -0x18 (%rbp), %rcx 0x108852488 <+56 >: cmpq $0 x0, %rcx 0x10885248c <+60 >: movq %rcx, -0x28 (%rbp) 0x108852490 <+64 >: je 0x1088524ab 0x108852496 <+70 >: movq -0x28 (%rbp), %rdi 0x10885249a <+74 >: callq 0x1088c240e 0x10885249f <+79 >: movq -0x28 (%rbp), %rax 0x1088524a3 <+83 >: movq %rax, %rdi 0x1088524a6 <+86 >: callq 0x1088c26b4 0x1088524ab <+91 >: xorl %eax, %eax -> 0x1088524ad <+93 >: addq $0 x30, %rsp 0x1088524b1 <+97 >: popq %rbp 0x1088524b2 <+98 >: retq 0x1088524b3 <+99 >: nopw %cs:(%rax,%rax) 0x1088524bd <+109 >: nopl (%rax)
结论 new 关键字会触发三个步骤:
调用函数operator new分配内存
调用对象的构造函数
返回对象的纯右值指针
delete 关键字会触发两个步骤
调用对象的析构函数
调用函数operator delete释放内存
operator new operator new函数负责实现内存的分配,分为全局实现和对象实现,可以通过重写该函数来自定义内存的分配。
STL实现 1 2 3 4 5 6 void * operator new (std::size_t size) ; void * operator new (std::size_t size, std::align_val_t alignment) ; void * operator new (std::size_t size, const std::nothrow_t &) noexcept ; void * operator new (std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept ; void * operator new (std::size_t size, void * ptr) noexcept ;
operator new(std::size_t size) 最常用的实现,其中的size就是要分配的内存大小,如果size是0也就是空类,那么会默认分配1个字节的内存。默认分配内存是通过malloc来实现的,如果分配失败则会调用get_new_handler函数,然后再次尝试分配内存,所以可以通过注册get_new_handler函数当内存不足时进行内存释放。在默认的实现中,如果开启了exception,则分配失败会抛出std::bad_alloc异常,否则会返回nullptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void *operator new (std::size_t size) _THROW_BAD_ALLOC { if (size == 0 ) size = 1 ; void * p; while ((p = ::malloc (size)) == nullptr ) { std::new_handler nh = std::get_new_handler (); if (nh) nh (); else #ifndef _LIBCPP_NO_EXCEPTIONS throw std::bad_alloc (); #else break ; #endif } return p; }
operator new(std::size_t size, const std::nothrow_t&) noexcept 该重载是针对不想使用异常的情况,在该实现内部会自己捕捉异常,失败会直接返回nullptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void * operator new (size_t size, const std::nothrow_t &) noexcept { void * p = nullptr ; #ifndef _LIBCPP_NO_EXCEPTIONS try { #endif p = ::operator new (size); #ifndef _LIBCPP_NO_EXCEPTIONS } catch (...) { } #endif return p; }
operator new(std::size_t size, void ptr) noexcept * 该重载就是placement new,多了一个指针参数,在该实现中不会分配内存,而是使用现有已分配内存,可以看出实现就是简单的返回参数指针。使用placement new可以方便的控制内存的分配,在STL内部大量的使用了该方法。
1 inline void * operator new (std::size_t , void * __p) _NOEXCEPT {return __p;}
在下面的示例中通过malloc自己分配了内存,然后通过placement new调用了A的构造函数。
1 2 A* a = static_cast <A*>(malloc (sizeof (A))); a = new (a) A ();
operator new(std::size_t size, std::align_val_t alignment) 该重载是C++17添加的新函数,align_val_t参数可以强制内存对齐。*STDCPP_DEFAULT_NEW_ALIGNMENT *是默认的内存对齐值,也就是new的地址是该值的倍数。 通过align_val_t参数可以指定new的内存对齐。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 void *operator new (std::size_t size, std::align_val_t alignment) _THROW_BAD_ALLOC { if (size == 0 ) size = 1 ; if (static_cast <size_t >(alignment) < sizeof (void *)) alignment = std::align_val_t (sizeof (void *)); void * p; while ((p = std::__libcpp_aligned_alloc(static_cast <std::size_t >(alignment), size)) == nullptr ) { std::new_handler nh = std::get_new_handler (); if (nh) nh (); else { #ifndef _LIBCPP_NO_EXCEPTIONS throw std::bad_alloc (); #else break ; #endif } } return p; }
下面的示例中将aligned_int对象的内存对齐指定为128,则分配的地址一定是128的倍数。
1 2 int * aligned_int = new (std::align_val_t (128 )) int ;bool b = intptr_t (aligned_int) % 128 == 0 ;
operator new(std::size_t size, std::align_val_t alignment, const std::nothrow_t&) noexcept 该重载是上面的无异常版本,内部会捕捉异常。
内存对齐 C++11后,可以通过alignas关键字来改变对齐值,通过alignof来查询对齐值。
默认情况下,struct按照内部最大类型来进行对齐,在Align结构体中,最大类型是int,所以其对齐值为4。char占1个字节,后面补上3个0字节,int占4个字节,所以sizeeof为8。
1 2 3 4 5 6 7 struct Align { char a; int b; }; size_t align2 = alignof (align); size_t size2 = sizeof (align);
通过alignas设置结构体的对齐值位16,char占1个字节,后面补上3个0字节,int占4个字节,后面补上8个0字节,所以sizeof为16。
1 2 3 4 5 6 7 struct alignas (16 ) Align { char a; int b; }; size_t align2 = alignof (align); size_t size2 = sizeof (align);
通过alignas设置结构体内部int的对齐值为8,则结构体最大对齐值为8,char占1个字节,后面补上7个0字节,int占4个字节,后面补上4个0字节,所以sizeof为16。
1 2 3 4 5 6 7 struct Align { char a; alignas (8 ) int b; }; size_t align2 = alignof (align); size_t size2 = sizeof (align);
通过alignas设置结构体内部char的对齐值为8,则结构体最大对齐值为8,char占1一个字节,后面有7个字节的空位,int占4个字节,完全够用,则int直接在这次对齐内分配,不需要重新再起一行,所以sizeof为8。
1 2 3 4 5 6 7 struct Align { alignas (8 ) char a; int b; }; size_t align2 = alignof (align); size_t size2 = sizeof (align);
自定义实现 operator new函数时可以自定义的,分为全局自定义和对象自定义,全局自定义的operator new会导致所有的对象的new都应用该自定义函数。而对象自定义只会影响到该对象创建。当两种实现都自定义时,对象自定义优先。
全局自定义
1 2 3 4 5 void * operator new (size_t size) { void * pVoid = malloc (size); return pVoid; }
对象自定义
1 2 3 4 5 6 7 8 9 10 class A { public : A () { std::cout << "constructor" << std::endl; } ~A () { std::cout << "destructor" << std::endl; } void * operator new (size_t size) { return ::operator new (size); } };
operator new[] operator new[]函数负责实现数组内存的分配,同样也分为全局实现和对象实现,可以通过重写该函数来自定义内存的分配。
长度信息存储 当创建的对象定义了析构函数时,为了在delete[] 时能够循环的调用每一个对象的析构函数,那么在new[]分配内存时会多分配一段内存,在这段内存中存储当前数据的长度信息,所以当new[]一个带析构函数的对象数组时,operator new[]的size参数会比整个数组长度要大,并且我们得到的数组指针并不是返回的void*地址,而是一个去掉前面一段的偏移地址。
在下面的实例中,当Align定义了析构函数时,size的值是11,当Align没有定义析构时,size的值是3。所以在这里用来存储数组长度的区域为8个字节,malloc分配的地址是0x7f8e40504b40,返回给a的地址是偏移8个字节后的0x7f8e40504b48。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 void * operator new [](size_t size) { std::cout << "call global new[] size: " << size << std::endl; void * pVoid = malloc (size); return pVoid; } class Align { public : ~Align () { std::cout << "destructor" << std::endl; } }; int main (int argc, char ** argv) { Align* a = new Align[3 ]; }
STL实现 1 2 3 4 5 6 7 void * operator new [](std::size_t size); void * operator new [](std::size_t size, std::align_val_t alignment) noexcept ; void * operator new [](std::size_t size, const std::nothrow_t &) noexcept ; void * operator new [](std::size_t size, std::align_val_t alignment, const std::nothrow_t &) noexcept ; void * operator new [](std::size_t size, void * ptr) noexcept ;
operator new[]的实现默认都是直接调用operator new来分配,这里就不展开了。
1 2 3 4 void * operator new [](size_t size) _THROW_BAD_ALLOC{ return ::operator new (size); }
自定义实现 全局自定义
1 2 3 4 void * operator new [](size_t size) { void * pVoid = malloc (size); return pVoid; }
对象自定义
1 2 3 4 5 6 7 8 9 10 class A { public : A () { std::cout << "constructor" << std::endl; } ~A () { std::cout << "destructor" << std::endl; } void * operator new [](size_t size) { return ::operator new [](size); } };
operator delete operator delete函数负责实现内存的释放,分为全局实现和对象实现,可以通过重写该函数来自定义内存的分配。operator delete需要和operator new配套实现,调用什么参数的operator new,内部在失败释放的时候就会调用什么参数的operator delete。
STL实现 1 2 3 4 5 6 7 8 9 void operator delete (void * ptr) noexcept ; void operator delete (void * ptr, std::size_t size) noexcept ; void operator delete (void * ptr, std::align_val_t alignment) noexcept ; void operator delete (void * ptr, std::size_t size, std::align_val_t alignment) noexcept ; void operator delete (void * ptr, const std::nothrow_t &) noexcept ; void operator delete (void * ptr, std:align_val_t alignment, const std::nothrow_t &) noexcept ; void operator delete (void * ptr, void *) noexcept ;
operator delete(void ptr) noexcept * operator delete函数默认的实现是调用free来进行内存的释放,之所以free能够正确的释放,是因为在malloc得到的地址其实是一个handler,在内部都有一个私有簿记(book keeping)区域来记录每个handler对应的内存区域是多大。所以malloc和free必须要配套使用,这样才能正确的释放内存。
1 2 3 4 void operator delete (void * ptr) noexcept { ::free (ptr); }
operator delete(void ptr, const std::nothrow_t&) noexcept *
1 2 3 4 void operator delete (void * ptr, const std::nothrow_t &) noexcept { ::operator delete (ptr) ; }
operator delete(void ptr, void ) noexcept**
1 inline void operator delete (void *, void *) _NOEXCEPT {}
operator delete[] operator delete[]函数负责实现数组内存的释放,同样也分为全局实现和对象实现,可以通过重写该函数来自定义内存的分配。delete[]和new[]一样,对于拥有析构函数对象的数组,其前面会有一段内存用来存储数组长度,但是传递到operator delete[]函数上时,是去掉偏移值的地址。
STL实现 1 2 3 4 5 6 7 8 9 10 void operator delete [](void * ptr) noexcept ; void operator delete [](void * ptr, std::size_t size) noexcept ; void operator delete [](void * ptr, std::align_val_t alignment) noexcept ; void operator delete [](void * ptr, std::size_t size, std::align_val_t alignment) noexcept ; void operator delete [](void * ptr, const std::nothrow_t &) noexcept ; void operator delete [](void * ptr, std::align_val_t alignment, const std::nothrow_t &) noexcept ; void operator delete [](void * ptr, void *) noexcept ;