new/delete/array new/array delete/placement new/重載new delete

new

在這裏插入圖片描述
當使用new的時候,實際上調用的是operator new,operator new在<new>頭文件中:

//@{
/** These are replaceable signatures:
 *  - normal single new and delete (no arguments, throw @c bad_alloc on error)
 *  - normal array new and delete (same)
 *  - @c nothrow single new and delete (take a @c nothrow argument, return
 *    @c NULL on error)
 *  - @c nothrow array new and delete (same)
 *
 *  Placement new and delete signatures (take a memory address argument,
 *  does nothing) may not be replaced by a user's program.
*/
void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)
  __attribute__((__externally_visible__));
void* operator new[](std::size_t) _GLIBCXX_THROW (std::bad_alloc)
  __attribute__((__externally_visible__));
void operator delete(void*) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void* operator new(std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void* operator new[](std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete(void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));
void operator delete[](void*, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
  __attribute__((__externally_visible__));

// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

// Default placement versions of operator delete.
inline void operator delete  (void*, void*) _GLIBCXX_USE_NOEXCEPT { }
inline void operator delete[](void*, void*) _GLIBCXX_USE_NOEXCEPT { }
//@}

new具體操作:

//頭文件先忽略
using std::new_handler;
using std::bad_alloc;// using 聲明
#if _GLIBCXX_HOSTED// 如果 有這個宏使用 std的malloc
using std::malloc;
#else// 沒有則使用c語言的malloc
// A freestanding C runtime may not provide "malloc" -- but there is no
// other reasonable way to implement "operator new".
extern "C" void *malloc (std::size_t);
#endif

_GLIBCXX_WEAK_DEFINITION void *
operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
{
  void *p;
  // new_handler 以後說明,但是可以看出首先我們根據入口參數 sz的大小分配內存, 
  // 如果sz爲0 則令其爲1 ,然後在while循環中調用malloc申請內存
  // 直到 申請成功 或者 拋出異常或者 abort
  /* malloc (0) is unpredictable; avoid it.  */
  if (sz == 0)
    sz = 1;

  while (__builtin_expect ((p = malloc (sz)) == 0, false))
    {
      new_handler handler = std::get_new_handler ();
      if (! handler)
    _GLIBCXX_THROW_OR_ABORT(bad_alloc());
      handler ();
    }

  return p;
}

其實在new的實現中都是使用一個while循環去一直malloc數據,如果malloc失敗的話,則調用_callnewh或者get_new_handler。侯捷說_callnewh是一種你可以設定的函數,那麼應該在_callnewh中釋放內存以便下次調用malloc的時候可以成功。

new出的指針與構造函數

使用new出的指針調用構造函數在某些編譯器上是可以的,在某些編譯器(gnu c)是不可以的。
但是使用new出的指針調用析構函數是可以的,只不過在調用delete的時候會再次調用析構函數。

#include <iostream>
using namespace std;
class A {
public:
  A() { cout << "ctor" << endl; };
  ~A() { cout << "dtor" << endl; };
private:
  int a;
};
int main()
{
  A* p = new A();
  // p->A();
  p->~A();
  delete p;
  return 0;
}

輸出爲:
在這裏插入圖片描述

delete

delete內部直接使用free.

array new/array delete

在這裏插入圖片描述
在delete new出來的數組的時候,如果錯誤使用了delete p而不是使用delete [] p;可能會引起內存泄漏,對沒有指針變量的對象可能沒有影響,對於有指針變量的對象就會產生內存泄漏。

#include <iostream>
using namespace std;
class A {
public:
  int id;
  A() { cout << "default ctor.this=" << this << " id=" << id << endl; };
  A(int _id) : id(_id) { cout << "ctor.this=" << this << " id=" << id << endl; };
  ~A() { cout << "dtor.this=" << this << " id=" << id << endl; };

};
int main()
{
  int size = 3;
  A* buf = new A[size];//調用3次構造函數, 0先於1先於2
  A* tmp = buf;
  cout << "buf=" << buf << " tmp=" << tmp << endl;

  for(int i = 0; i < size; i++)
    new(tmp++)A(i);//placement new
  cout << "buf=" << buf << " tmp=" << tmp << endl;

  delete[] buf;//dtor調用3次, 先析構2再析構1最後析構0

  return 0;
}

輸出:

default ctor.this=0x866d84 id=8782016
default ctor.this=0x866d88 id=0
default ctor.this=0x866d8c id=0
buf=0x866d84 tmp=0x866d84
ctor.this=0x866d84 id=0
ctor.this=0x866d88 id=1
ctor.this=0x866d8c id=2
buf=0x866d84 tmp=0x866d90
dtor.this=0x866d8c id=2
dtor.this=0x866d88 id=1
dtor.this=0x866d84 id=0

可以看出,在析構的時候,析構順序是從最後一個到第一個。

placement new

  • placement new允許在已經分配好的空間上構造對象。
  • 這個內存空間可以不是動態分配的。
  • placement new不能被重載。
  • 對於需要頻繁new delete的情景,如果只是簡單地調用new delete不僅會比較慢(尋找合適的內存塊需要時間),還會造成內存碎片。我們可以事先分配好內存,每次在這片內存上使用placement new構造出對象。
    使用方法:
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }
inline void* operator new[](std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

第二個參數是一個指針,指向已經分配好的內存,然後直接在這片內存上通過new構造對象,它直接將傳入的指針返回。
在這裏插入圖片描述
在這裏插入圖片描述
上圖可以看出placement new被編譯器解釋爲3步,首先調用operator new函數,然後強制類型轉換成對象類型的指針,最後一步調用構造函數。

重載::operator new和::operator delete

全局的operator new以及operator delete是可以重載的,但一般不進行重載,因爲這會影響到很多地方。

void* myAlloc(size_t size)
{ return malloc(size); }

void* myFree(void* ptr)
{ return free(ptr); }

inline void* operator new(size_t size)
{ 
	cout << "my operator new" << endl;
	return myAlloc(size);
}
inline void* operator new[](size_t size)
{ 
	cout << "my operator new[]" << endl;
	return myAlloc(size);
}
inline void* operator delete(void* ptr)
{ 
	cout << "my operator delete" << endl;
	return myFree(ptr);
}
inline void* operator delete[](void* ptr)
{ 
	cout << "my operator delete[]" << endl;
	return myFree(ptr);
}

在類中重載operator new與operator delete

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;

class Foo
{
public:
  int _id;
  // long _data;

public:
  Foo() : _id(0) {  
    cout << "default ctor.this=" << this << " id=" << _id << endl;
  };
  Foo(int i) : _id(i) {
   cout << "ctor.this=" << this << " id=" << _id << endl;
  };
  ~Foo() { cout << "dtor.this=" << this << " id=" << _id << endl; };
  
  //這裏必須是static
  static void* operator new(size_t size);
  static void operator delete(void* pdead, size_t size);
  static void* operator new[](size_t size);
  static void operator delete[](void* pdead, size_t size);
};

void* Foo::operator new(size_t size){
  Foo* p = (Foo*)malloc(size);
  cout << "Foo operator new, size=" << size <<"\treturn " << p << endl;
  return p;
}

void Foo::operator delete(void* pdead, size_t size){
  cout << "Foo operator delete, pdead=" << pdead << " size=" << size << endl;
  free(pdead);
}

void* Foo::operator new[](size_t size){
  Foo* p = (Foo*)malloc(size);
  cout << "Foo operator new[], size=" << size <<"\treturn " << p << endl;
  return p;
}

void Foo::operator delete[](void* pdead, size_t size){
  cout << "Foo operator delete[], pdead=" << pdead << " size=" << size << endl;
  free(pdead);
}
int main()
{
  Foo* p = new Foo(7);
  delete p;

  Foo* pArray = new Foo[5];
  delete [] pArray;
  return 0;
}

注意在類中重載的operator new等函數必須是static,因爲如果自己在類中重載了operator new,那麼分配內存時就會調用自己重載的那個new。所以如果new不是static的,調用new的時候還沒有對象產生,又該如何new出對象呢?將new聲明爲static就可以在沒有對象的時候還能調用自己重載的operator new了,因爲靜態成員函數不需要對象生成就可以調用。當將運算符函數定義爲類的成員時,他們是隱式靜態的,無需顯式地聲明static。

上述代碼的輸出爲:

Foo operator new, size=4	return 0xeca040
ctor.this=0xeca040 id=7
dtor.this=0xeca040 id=7
Foo operator delete, pdead=0xeca040 size=4
Foo operator new[], size=24	return 0xec0578
default ctor.this=0xec057c id=0
default ctor.this=0xec0580 id=0
default ctor.this=0xec0584 id=0
default ctor.this=0xec0588 id=0
default ctor.this=0xec058c id=0
dtor.this=0xec058c id=0
dtor.this=0xec0588 id=0
dtor.this=0xec0584 id=0
dtor.this=0xec0580 id=0
dtor.this=0xec057c id=0
Foo operator delete[], pdead=0xec0578 size=24

可以看出,如果自己在類中重載了operator new,那麼會繞過全局的operator new,自動調用自己重載的operator new。當然也可以在new前面加上::表示使用全局的new而不是自己定義的new。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章