new和malloc的區別--面試問題及其延伸

參考博客:https://blog.csdn.net/zhong29/article/details/80930919

malloc的實現方式

malloc函數將內存空間中可用的內存塊連接成一個很長的空閒鏈表。當調用malloc函數的時候,就會在鏈表中進行遍歷,直到找到滿足用戶申請空間大小的內存塊,然後就從鏈表中把這段內存拿出來分配給用戶,剩下的接回原空閒鏈表。如果無法獲取符合大小的內存塊的時候,就會放回NULL指針,因此如果用malloc申請空間就一定要進行返回值判斷。
同時用malloc返回的指針是對齊的,爲了能夠使得void*適用於任何對象。大多數實現所分配的空間都會比申請空間來的大一些,額外的空間用來記錄管理信息——分配塊的長度、指向下一個分配塊的指針。

當用戶調用free函數的時候就把申請的內存塊重新接到空閒鏈表上。

這裏提一下realloc,realloc函數可以增減之前分配的空間的長度,如果發現當前的存儲區域的高位地址上沒有足夠的空間了,就會分配一個新的足夠大的存儲區域,將現有的內容複製過去,同時釋放原有存儲區域的內容。這裏要注意的是,因爲realloc可能會移動位置,如果用指針指向該地址,就有可能使得指針無效

new的實現

先看下new的源碼

_GLIBCXX_WEAK_DEFINITION void *
    operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc)
    {
      void *p;

      /* malloc (0) is unpredictable; avoid it.  */
      if (sz == 0)
        sz = 1;
      p = (void *) malloc (sz);
      while (p == 0)
        {
          new_handler handler = std::get_new_handler ();
          if (! handler)
            _GLIBCXX_THROW_OR_ABORT(bad_alloc());
          handler ();
          p = (void *) malloc (sz);
        }

      return p;
    }

從源碼可以看出實際上new也只是申請了內存空間。這裏涉及到一個new_handler,他的作用就是反覆申請內存空間,如果這個new_handler做不了這個工作,那麼就換新的new_handler,如果沒有new_handler能做這個工作就把一個空指針傳給handler,就可以實現異常的拋出。

總結下new_handler的工作:

  • Make more memory available,使得下一次內存分配能夠成功,如刪除其他無用的內存,使得系統有更多可用的內存。
  • Install a different new_handler,如果當前的new_handler分配不了這麼多內存,或者他知道另一new_handler能完成,則可以調用set_new_handler來設置另一個new_handler
  • Deinstall the new_handler,就是如果沒有能分配這麼大內存的handler,就會把空指針返回。這樣就可以拋出異常
  • Throw an exception,類型爲bad_alloc的異常不會被operator new捕獲,會被傳播到內存請求的地方。

那麼new的過程也可以理解了,首先調用operator new函數申請一片空間,然後改變指針類型從void* -> 某個指針類型,再進行構造函數的調用

pt=static_cast(operator new(sizeof(T)));
pt->T();

new和malloc的異同

  1. new和malloc都是在堆上開闢內存。malloc只開闢了空間,沒有初始化,而new不僅開闢了空間還進行了初始化。
  2. malloc需要傳入初始化的字節數,指定開闢空間的大小,而new就不用了,new的工作方式是就是上面代碼中顯示的,按照對象的大小獲取字節數,然後開闢空間大小。
  3. malloc失敗返回NULL,new失敗返回的是bad_alloc的異常,因此new對象需要異常捕獲來判斷(其實可以不用異常捕獲,但是判斷是一定需要的)
  4. malloc用free來釋放,new用delete來釋放,new[]—delete[]
  5. malloc只有一種開闢內存的方式,而new分爲普通new,nothrow的new,const new 和定位new

擴展知識1

  • nothrow new
#include<new>
Manager* pManager = new (std::nothrow) Manager();
if(NULL==pManager)
{
    return false;
}

//或者設置set_new_handle 自定義異常拋出函數
#include <new>
#include <iostream>
#include <stdlib.h>

using namespace std;

void __cdecl newhandler()
{
    return;
}

int main()
{
    set_new_handler (newhandler);

    Manager * pManager = new (std::nothrow) Manager();
    if(NULL == pManager)
    {
        //記錄日誌
        return false;
    }
}
  • const new

暫不整理,

瞭解下常量指針和指針常量的區別
常量指針:const int *p=&a; 常量指針,這是一個指針,不過指向的內容是常量。const修飾數據類型,地址可以改,但是內容不可改

int a=1,c=2;
const int *b=&a;
b=&c;//對的
*b=2;//報錯

指針常量:int * const p=&a; 指針常量,這是一個常量,就是指針本身是常量。因此地址不可以改,但是內容可以改。

int a=1,c=2;
int * const b=&a;
b=&c;//錯
*b=2;//對
  • placement new
    就是在指定的位置上來實現對象。
// Default placement versions of operator new.
inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
{ return __p; }

從源碼來看placement new沒有申請新的空間,直接返回了第二個參數的指針。
那麼placement new的過程應該是

T p=new(alreadyExistPoint) T()

//分解步驟如下
void* p1=::operator new(sizeof(T),alreadyExistPoint);
T* p=static_cast<T*> p1;

擴展知識2

存儲空間的佈局

C程序一直由幾個部分組成

  • 正文段。這裏是CPU執行的機器指令部分。通常正文段是可共享的,只讀的。
  • 初始化數據段。 包含了程序中需明確的賦予初值的變量,例如除了函數外的聲明 int maxcount=999;
  • 未初始化數據段。 在程序開始執行之前,內核將此段的數據賦予初值或者空指針。
  • 棧。 自動變量和每次函數調用時所需要保存的信息都存放在此段中。每次函數調用時,其返回地址以及調用者的環境信息都存放在棧中。 對於遞歸函數,每次自身被調用時,就會產生一個棧幀,因此函數之間的變量集就不會互相干擾。
  • 堆。 通常在堆中進行動態存儲的分配。
    以上摘自UNIX環境高級編程

從上面的描述中我們可以知道棧是由編譯器自動分配和釋放的。而堆是由程序員來操作的。

如何只在堆上或者只在棧上創建對象呢。

簡單說下,只在堆上就是隻能通過new的方式來動態分配空間,因此就要想辦法禁用在編譯期間就創建對象的方式如 A a(args); 禁用的辦法就是講構造析構函數設爲protected函數,那麼編譯器就不會在編譯期間開闢棧空間給他。

class A{
protected:
	A(){};
	~A(){};
public:
	static A* create(){
		return new A();
	}	
	void destory(){
		delete this;//直接刪除傳入的this對象,成員函數能調用protected對象
	}
};

接下來就是隻在棧上生成對象,很簡單就把operator new和operator delete禁用掉即可

class A{
private:
	void* operator new(size_t){};
	void operator delete(void* ptr){};
public:
	A(){};
	~A(){};
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章