當前位置:首頁 > 公眾號精選 > C語言與CPP編程
[導讀]C內(nèi)存管理(一)導語c內(nèi)存管理學習自侯捷。下面是本次對C內(nèi)存管理一些筆記。1.四種內(nèi)存分配與釋放在編程時可以通過上圖的幾種方法直接或間接地操作內(nèi)存。下面將介紹四種C內(nèi)存操作方法:對于GNUC:四種分配與釋放方式如下://C函數(shù)void*p1=malloc(512);*(int*)...

C 內(nèi)存管理(一)

導語

c 內(nèi)存管理學習自侯捷。

下面是本次對C 內(nèi)存管理一些筆記。

1.四種內(nèi)存分配與釋放

在編程時可以通過上圖的幾種方法直接或間接地操作內(nèi)存。下面將介紹四種C 內(nèi)存操作方法:

對于GNU C:四種分配與釋放方式如下:

// C函數(shù) void *p1 = malloc(512); *(int *) p1 = 100; cout << *(int *) p1 << endl; free(p1);
// C 表達式 int *p2 = new int(10); cout << *p2 << endl; delete p2;
// C 函數(shù) 實際上等價于上述malloc與free void *p3 = ::operator new(512); *(int *) p3 = 103; cout << *(int *) p3 << endl; ::operator delete(p3);
//C 標準庫 printf("hello gcc %d\n", __GNUC__);#ifdef __GNUC__// 以下函數(shù)都是non-static,一定要通過object調(diào)用,以下分配7個單元,而不是7個字節(jié) int *p4 = allocator<int>().allocate(7); *p4 = 9; cout << *p4 << endl; allocator<int>().deallocate((int *) p4, 7);
/** * void *p = alloc::allocate(512); 分配512bytes * alloc::deallocate(p,512); */ // __pool_alloc等價于之前的alloc 9個單元 int *p5 = __gnu_cxx::__pool_alloc<int>().allocate(9); *p5 = 10; cout << *p5 << endl; __gnu_cxx::__pool_alloc<int>().deallocate((int *) p5, 9);#endif

2.new/delete表達式

2.1 new表達式

當使用operator new

// 下面這個是new expression,而operator new 是函數(shù)Complex* pc = new Complex(1,2);上述會被編譯器轉(zhuǎn)為:

Complex *pc;try {// operator new 實現(xiàn)自 new_op.cc void* mem = operator new(sizeof(Complex)); //allocate 分配內(nèi)存 pc = static_cast(mem); // cast 轉(zhuǎn)型 以符合對應的類型,這里對應為Complex* pc->Complex::Complex(1,2); // construct // 注意:只有編譯器才可以像上面那樣直接呼叫ctor 欲直接調(diào)用ctor可通用placement new: new(p) Complex(1,2);}catch(std::bad_alloc) { // 若allocation失敗就不執(zhí)行constructor}new操作背后編譯器做的事:

  • 第一步通過operator new()操作分配一個目標類型的內(nèi)存大小,這里是Complex的大??;

  • 第二步通過static_cast將得到的內(nèi)存塊強制轉(zhuǎn)換為目標類型指針,這里是Complex*

  • 第三版調(diào)用目標類型的構(gòu)造方法,但是需要注意的是,直接通過pc->Complex::Complex(1, 2)這樣的方法調(diào)用構(gòu)造函數(shù)只有編譯器可以做,用戶這樣做將產(chǎn)生錯誤。

注意:operator new()操作的內(nèi)部是調(diào)用了malloc()函數(shù)。

operator new()具體實現(xiàn)源代碼見:

https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/new_op.cc

2.2 delete表達式

對于上述delete調(diào)用,

delete pc;pc->~Complex(); //先析構(gòu)operator delete(pc); //然后釋放內(nèi)存delete操作步驟:

  • 第一步調(diào)用了對象的析構(gòu)函數(shù)

  • 第二步通過operator delete()函數(shù)釋放內(nèi)存,本質(zhì)上也是調(diào)用了free函數(shù)。

operator delete()具體實現(xiàn)源代碼見:

https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/del_op.cc

3.array new/array delete

3.1 array

上圖主要展示的是關(guān)于array new內(nèi)存分配的大致情況。

當new一個數(shù)組對象時(例如 new Complex[3]),編譯器將分配一塊內(nèi)存,這塊內(nèi)存首部是關(guān)于對象內(nèi)存分配的一些標記,然后下面會分配三個連續(xù)的對象內(nèi)存,在使用delete釋放內(nèi)存時需要使用delete[]。

什么情況下發(fā)生內(nèi)存泄露?

如果不使用delete[],只是使用delete只會將分配的三塊內(nèi)存空間釋放,但不會調(diào)用對象的析構(gòu)函數(shù),如果對象內(nèi)部還使用了new指向其他空間,如果指向的該空間里的對象的析構(gòu)函數(shù)沒有意義,那么不會造成問題,如果有意義,那么由于該部分對象析構(gòu)函數(shù)不會調(diào)用,那么將會導致內(nèi)存泄漏。

圖中new string[3]便是一個例子,雖然str[0]、str[1]、str[2]被析構(gòu)了,但只是調(diào)用了str[0]的析構(gòu)函數(shù),其他對象的析構(gòu)函數(shù)不被調(diào)用,這里就會出問題。

其中的cookie保存的是delete[]里面的數(shù)據(jù),比如delete幾次。

3.2 演示數(shù)組對象創(chuàng)建與析構(gòu)過程

構(gòu)造函數(shù)調(diào)用順序是按照構(gòu)建對象順序來執(zhí)行的,但是析構(gòu)函數(shù)執(zhí)行卻相反。

構(gòu)造函數(shù):自上而下;析構(gòu)函數(shù):自下而上。

3.3 malloc基本構(gòu)成

如果使用new分配十個內(nèi)存的int,內(nèi)存空間如上圖所示,首先內(nèi)存塊會有一個頭和尾,黃色部分為debug信息,灰色部分才是真正使用到的內(nèi)存,藍色部分的12bytes是為了讓該內(nèi)存塊以16字節(jié)對齊。在這個例子中delete pi和delete[] pi效果是一樣的,因為int沒有析構(gòu)函數(shù)。但是如果釋放的對象的析構(gòu)函數(shù)有意義,array delet就必須采用delete[],否則發(fā)生內(nèi)存泄露。

4.placement new

char *buf = new char[sizeof(Complex) * 3];Complex *pc = new(buf)Complex(1, 2);delete[]buf;上述被編譯器編譯為:

Complex *pc;try void* mem = operator new(sizeof(Complex),buf); //allocate pc= static_cast(mem);//cast pc->Complex::Complex(1,2);//construct} catch (std::bad_alloc) { // 若allocation失敗就不執(zhí)行construct}值得注意的是,這里采用的operator new有兩個參數(shù),我們在下面源碼中:

https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/new

看到:

_GLIBCXX_NODISCARD inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT{ return __p; }因此得出,沒有做任何事,直接返回buf, 因此placement new 就等同于調(diào)用構(gòu)造函數(shù)。也沒有所謂的operator delete ,因為placement new根本沒有分配memory。

5.重載

5.1 C 內(nèi)存分配的途徑

如果是正常情況下,調(diào)用new之后走的是第二條路線,如果在類中重載了operator new(),那么走的是第一條路線,但最后還是要調(diào)用到系統(tǒng)的::operator new()函數(shù),這在后續(xù)的例子中會體現(xiàn)。

對于GNU C,背后使用的allocate()函數(shù)最后也是調(diào)用了系統(tǒng)的::operator new()函數(shù)。

5.2 重載new 和 delete

上面這張圖演示了如何重載系統(tǒng)的::operator new()函數(shù),該方法最后也是模擬了系統(tǒng)的做法,效果和系統(tǒng)的方法一樣,但一般不推薦重載::operator new()函數(shù),因為它對全局有影響,如果使用不當將造成很大的問題。

如果是在類中重載operator new()方法,那么該方法有N多種形式,但必須保證函數(shù)參數(shù)列表第一個參數(shù)是size_t類型變量;對于operator delete(),第一個參數(shù)必須是void* 類型,第二個size_t是可選項,可以去掉。

對于operator new[]和operator delete[]函數(shù)的重載,和前面類似。

6.pre-class allocator1

前面把基本元素的重載元素學完了,例如:new、operator new、array new等等。萬事俱備,現(xiàn)在可以開始一個class進行內(nèi)存管理。

對于malloc來說,大家都有一個誤解,以為它很慢,其實它不慢,后面會講到。無論如何,減少malloc的調(diào)用次數(shù),總是很好的,所以設計class者,可以先挖一塊,只使用一次malloc,使用者使用,就只需要調(diào)用一次malloc,這樣就是一個小型的內(nèi)存管理。

除了降低malloc次數(shù)之外,還需要降低cookie用量。前面提到一次malloc需要一組(兩個)cookie,總共8字節(jié)。

所以,如果一次要1000個大小,這1000個切下來,都是不帶cookie,只有1000個一整包上下帶cookie。所以內(nèi)存池的設計就是一整塊,一個池塘。這一大塊設計不但要提升速度,而且要降低浪費率。所以內(nèi)存管理目標就是,一個是速度,一個是空間。

每次挖一大塊,需要指針把他們穿起來,如下圖右邊鏈表結(jié)構(gòu),基于這個考量,下面例子中設計了next指針。此時碰到了一個困惑:多設計了一個指針,去除了cookie,卻膨脹率100%(int i 占4字節(jié),指針也是4字節(jié))。

使用者使用new的時候,就會被接管到operator new這個函數(shù)來,delete類似。

分配:operator new就是挖一大塊,里面主要做的就是指針操作與轉(zhuǎn)型。其中freeStore指向頭,operator new返回的就是freeStore表頭。

回收:當使用者delete一個Scree,就會先調(diào)用析構(gòu)函數(shù),然后調(diào)用釋放內(nèi)存函數(shù),operator delete接管了這個任務,接收到一個指針。就把這個鏈表回收到單向鏈表之中。單向鏈表始終都有一個頭,所以回收動作最快放在鏈表開頭。

7.pre-class allocator2

這里與上述不同之處在于使用union設計,這里帶來了一個觀念:嵌入式指針,embedding pointer。

分配與釋放同前面6。

嵌入式指針:rep占16字節(jié),next占前8字節(jié)。

union { AirplaneRep rep; //此針對 used object Airplane* next; //此針對 free list};借用一個東西的前8字節(jié)當指針用,這樣整體上可以節(jié)省空間,這是一個很好的想法,在內(nèi)存管理中都是這么來用。

最后,6與7中的operator delete并沒有free掉,只是回收到單向鏈表中。這樣子好?

這種當然不好,技術(shù)難點非常高,后面談!雖然沒有還給操作系統(tǒng),但不能說它內(nèi)存泄露,因為這些都在它的"手上"。

8.static allocator3

不要把內(nèi)存分配與回收寫在各個class中,而要把它們集中在一個allocator中!

在前面設計中,每次都需要重載相應的函數(shù),內(nèi)部處理一些邏輯,重復代碼量多,我們可以將這些包裝起來,使它容易被重復使用。以下展示一個作法:每個allocator object都是個分配器,在allocator設計了allocate與deallocate兩個函數(shù)。,它內(nèi)部設計如下:

class allocator{private: struct obj { struct obj* next; //embedded pointer };public: void* allocate(size_t); void deallocate(void*, size_t); void check();
private: obj* freeStore = nullptr; const int CHUNK = 5; //小一點方便觀察 標準庫里面是20};

其他類,例如:Foo和Goo,當需要allocator這種內(nèi)存管理池,只需要寫出下面兩個函數(shù):

static void* operator new(size_t size){ return myAlloc.allocate(size);}static void operator delete(void* pdead, size_t size){ return myAlloc.deallocate(pdead, size);}然后把內(nèi)部做的動作交給myAlloc。myAlloc是專門為Foo或者Goo之類的服務的,可以設計為靜態(tài) :

static allocator myAlloc;想象成里面有一根指針指向一條鏈表,專門為自己服務。

這里實現(xiàn)同前面的實現(xiàn)。

void* allocator::allocate(size_t size){ obj* p;
if (!freeStore) { //linked list 是空的,所以攫取一大塊 memory size_t chunk = CHUNK * size; freeStore = p = (obj*)malloc(chunk);
//cout << "empty. malloc: " << chunk << " " << p << endl;
//將分配得來的一大塊當做 linked list 般小塊小塊串接起來 for (int i = 0; i < (CHUNK - 1); i) { //沒寫很漂亮, 不是重點無所謂. p->next = (obj*)((char*)p size); p = p->next; } p->next = nullptr; //last } p = freeStore; freeStore = freeStore->next;
//cout << "p= " << p << " freeStore= " << freeStore << endl;
return p;}同前面實現(xiàn):

void allocator::deallocate(void* p, size_t){ //將 deleted object 收回插入 free list 前端 ((obj*)p)->next = freeStore; freeStore = (obj*)p;}這樣設計好之后,任何一個class要使用它,這種寫法比較干凈,application classes不再需內(nèi)存分配糾纏不清,所有相關(guān)細節(jié)交給allocator去操心。

9.macro for static allocator4

之前的幾個版本都是在類的內(nèi)部重載了operator new()和operator delete()函數(shù),這些版本都將分配內(nèi)存的工作放在這些函數(shù)中,但現(xiàn)在的這個版本將這些分配內(nèi)存的操作放在了allocator類中,這就漸漸接近了標準庫的方法。

從上面的代碼中可以看到,兩個類Foo和Goo中operator new()和operator delete()函數(shù)等很多部分代碼類似,于是可以使用來將這些高度相似的代碼提取出來,簡化類的內(nèi)部結(jié)構(gòu),但最后達到的結(jié)果是一樣的。

//DECLARE_POOL_ALLOC -- used in class definition#define DECLARE_POOL_ALLOC() \public:\ void* operator new(size_t size) { \ return myAlloc.allocate(size); \ } \ void operator delete(void* p) { \ myAlloc.deallocate(p, 0); \ } \protected: \ static light::allocator myAlloc;
//IMPLEMENT_POOL_ALLOC -- used in class implementation#define IMPLEMENT_POOL_ALLOC(class_name) \light::allocator class_name::myAlloc;Foo、Goo:

class Foo {DECLARE_POOL_ALLOC()public: long L; string str;public: Foo(long l): L(l) { }};
IMPLEMENT_POOL_ALLOC(Foo)
class Goo {DECLARE_POOL_ALLOC()public: complex<double> c; string str;public: Goo(const complex<double> x): c(x) { }};
IMPLEMENT_POOL_ALLOC(Goo)

10.global allocator

前面設計了版本1、2、3、 4。

版本1:最簡單,版本2:加上了embedding pointer,版本3:把內(nèi)存的動作抽取到class中,版本4:設計一個macro。

上面我們自己定義的分配器使用了一條鏈表來管理內(nèi)存的,但標準庫卻用了多條鏈表來管理,這在后續(xù)會詳細介紹:

11.new handler

當operator new無法滿足某一內(nèi)存分配需求時,它會拋出std::bad_alloc exception。某些編譯器則返回0,你可以另編譯器那么做:new(nothrow) Foo;

在拋出異常之前,它會調(diào)用一個客戶指定的錯誤處理函數(shù),也就是所謂的new-handler。

客戶通過調(diào)用set_new_handler來設置new-handler:

namespace std {typedef void (*new_handler)();new_handler set_new_handler(new_handler p) throw();}set_new_handler返回之前設置的new_handler。

當operator new無法滿足內(nèi)存申請時,它會不斷調(diào)用new-handler函數(shù),直到找到足夠內(nèi)存。因此,一個設計良好的new-handler必須做以下事:

a:讓更多內(nèi)存可被使用,以便使operator new下一次分配內(nèi)存能夠成功。實現(xiàn)方法之一就是程序一開始就分配一大塊內(nèi)存,而后當new-handler第一次被調(diào)用時,將它們還給程序使用;

b:安裝另一個new-handler:如果目前的new-handler無法獲得更多內(nèi)存,并且它直到另外哪個new-handler有此能力,則當前的new-handler可以安裝那個new-handler以替換自己,下次當operator new調(diào)用new-handler時,就是調(diào)用最新的那個。

c:卸載new-handler,一旦沒有設置new-handler,則operator new就會在無法分配內(nèi)存時拋異常;

d:拋出bad_alloc異常;

e:不返回,直接調(diào)用abort或exit。

c 設計是為了給我們一個機會,因為一旦內(nèi)存不足,整個軟件也不能運作,所以它借這個機會通知你,也就是通過set_new_handler調(diào)用我們的函數(shù),由我們來決定怎么辦。

現(xiàn)在回過頭看operator new源碼:

如果malloc沒有成功,handler函數(shù)會循環(huán)調(diào)用,除非我們將handler設置為空,或者在handler中拋出異常。

operator new (std::size_t sz) _GLIBCXX_THROW (std::bad_alloc){ void *p;
/* malloc (0) is unpredictable; avoid it. */ if (__builtin_expect (sz == 0, false)) sz = 1;
while ((p = malloc (sz)) == 0){ new_handler handler = std::get_new_handler (); if (! handler) //利用NULL,跑出錯誤異常 _GLIBCXX_THROW_OR_ABORT(bad_alloc()); handler (); // 重新設定為原來的函數(shù)}
return p;}例子:

#include #include #include
using namespace std;
void noMoreMemory() { cerr<<"out of memory"; abort();}

int main() { set_new_handler(noMoreMemory); int *p=new int[900000000000000]; assert(p);}輸出:

out of memory

12.=default和=delete

(=default與=delete) it is not only for constructors and assignments, but also applies to operator new/new[], operator delete/delete[] and their overloads.

解釋一下,=default和=delete不僅適用于構(gòu)造函數(shù)和賦值,還適用于operator new / new [],operator delete / delete []及其重載。

C 的類有四類特殊成員函數(shù),它們分別是:默認構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)以及拷貝賦值運算符。這些類的特殊成員函數(shù)負責創(chuàng)建、初始化、銷毀,或者拷貝類的對象。如果程序員沒有顯式地為一個類定義某個特殊成員函數(shù),而又需要用到該特殊成員函數(shù)時,則編譯器會隱式的為這個類生成一個默認的特殊成員函數(shù)。

(1)C 11 標準引入了一個新特性:"=default"函數(shù)。

程序員只需在函數(shù)聲明后加上“=default;”,就可將該函數(shù)聲明為 "=default"函數(shù),編譯器將為顯式聲明的 "=default"函數(shù)自動生成函數(shù)體。

class X {public:X() = default;}
  • "=default"函數(shù)特性僅適用于類的特殊成員函數(shù),且該特殊成員函數(shù)沒有默認參數(shù)。

class X1{public: int f() = default; // err , 函數(shù) f() 非類 X 的特殊成員函數(shù) X1(int, int) = default; // err , 構(gòu)造函數(shù) X1(int, int) 非 X 的特殊成員函數(shù) X1(int = 1) = default; // err , 默認構(gòu)造函數(shù) X1(int=1) 含有默認參數(shù)};
  • "=default"函數(shù)既可以在類體里(inline)定義,也可以在類體外(out-of-line)定義。

class X2{public: X2() = default; //Inline defaulted 默認構(gòu)造函數(shù) X2(const X
本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉