c++ 記憶體管理

来源:https://www.cnblogs.com/flysong/archive/2018/01/01/8167371.html
-Advertisement-
Play Games

c++中給對象分配記憶體常見有三種方法: 使用c++ 庫函數 std::allocator (c++ library); 使用new,new[] 表達式,::operator new() 操作符,(c++ primitives); c 函數 malloc/free (CRT); 測試代碼如下: 1 # ...


  c++中給對象分配記憶體常見有三種方法:

  • 使用c++ 庫函數 std::allocator  (c++ library);
  • 使用new,new[] 表達式,::operator new() 操作符,(c++ primitives);
  • c 函數 malloc/free (CRT);

測試代碼如下:

 1 #include<iostream>
 2 #include <stdlib.h>
 3 #include<complex>
 4 #include <memory>    //內含 std::allocator
 5 #include <ext\pool_allocator.h> // __pool_alloc
 6 
 7 using namespace std;
 8 
 9 void test()
10 {
11     void* p1 = malloc(512); //512bytes
12     free(p1);
13 
14     complex<int>* p2 = new complex<int>; //one object
15     delete p2;
16 
17     void* p3 = ::operator new(512);// 512bytes
18     ::operator delete(p3);
19 
20 #ifdef __GNUC__
21     //以下函數都是non-static,一定要通過object調用,分配7個int的記憶體
22     void* p4 = allocator<int>().allocate(7);//allocator<int>()創建臨時對象
23     allocator<int>().deallocate((int*)p4,7);
24 
25     //void* p5 = alloc::allocate(512);  //2.9
26     //alloc::deallocate(p5,512);
27 
28     void* p6 = __gnu_cxx::__pool_alloc<int>().allocate(9); // 對應上面的 gnc2.9;
29     __gnu_cxx::__pool_alloc<int>().deallocate((int*)p6,9);
30 #endif // __GNUC__
31 
32 #ifdef _MSC_VER_
33     int* p6 = allocator<int>().allocate(3, (int*)0);
34     allocator<int>().deallocate(p6,3);
35 #endif // _MSC_VER_
36     return;
37 }
View Code

一、new

使用new表達式,編譯器將其轉化為先調用 operator new 運算符,然後調用構造函數。 new過程是先分配記憶體,然後調用構造函數;delete時,先調用析構函數,然後釋放記憶體。

image image
只有編譯器可以直接調用構造函數,示例代碼如下:
class A
{
public:
    int id;

    A() : id(0)
    {
        cout << "default ctor.this=" << this << " id=" << id << endl;
    }
    A(int i):id(i)
    {
        cout<< "ctor. this = " << this <<" id=" <<id <<endl;
    }
    ~A()
    {
        cout<<"dtor.this = "<<this <<endl;
    }
};
void testCt()
{
    string* pstr = new string;
    cout << "str = " << *pstr <<endl;

    // pstr->string::string("123"); //'class std::basic_string<char>' has no member named 'string'|
    pstr->~string();
    cout<<"str = " <<endl;

    A* pA = new A(1);
    cout<< "pA->id = "<< pA->id<<endl;

    // pA->A::A(3);//error: cannot call constructor 'A::A' directly|

    // A::A(5); //error: cannot call constructor 'A::A' directly [-fpermissive]|

    cout<< "pA->id = "<<pA->id<<endl;
    delete pA;
    A* pA2;
    new(pA2)A(5);
    cout<< "pA2->id = "<<pA2->id<<endl;
    delete pA2;
}
View Code

二、Array new, Replacement new 

image    定義數組A* p = new A[3];時,會申請分配記憶體,並調用三次預設構造函數;   當使用 delete[] p;時,會調用三次析構函數,並釋放記憶體,而使用delete p,則數組所占記憶體仍然會釋放掉,但只會調用一次析構函數,而如果析構函數內有釋放記憶體的操作,則使用delete p,造成記憶體泄漏。 測試代碼如下:
 1 void testArrNew()
 2 {
 3     A* buf = new A[3]; //預設構造函數調用3次 調用順序 0-1-2
 4     //A必須有預設構造函數 new A[3]調用的是預設構造函數
 5 
 6     A* tmp = buf;//記錄A數組的起點位置
 7 
 8     cout << "buf=" << buf << " tmp=" << tmp << endl;
 9 
10     for(int i = 0; i < 3; i++)
11     {
12         new(tmp++)A(i); //placement new;在分配好的記憶體上,賦值
13     }
14 
15     cout << "buf=" << buf << " tmp=" << tmp << endl;
16 
17     //delete[] buf;
18     delete buf;
19 }
View Code

執行結果如下:

image image

三、 placement new

先看一下placement new

char* buf = new char[sizeof(A) * 3];//申請記憶體

A* pc = new(buf)A();//在申請好的buf的記憶體,在buf上賦值

代碼中 new(buf)A(); 就是placement new.

編譯器會將上述代碼轉化為

A * pc;
try {
    void* men = operator new(sizeof(A), buf); //申請記憶體
    pc = static_cast<A*>(mem);//轉換
    pc->A::A();//構造函數
}
catch (std::bad_alloc){
  
}

四、重載 operator new, operator new[], operator delete, operator delete[]

接管全局new,delete 函數,重載使用自己的操作符。

測試代碼如下:

  新建class Foo類

 1 class Foo
 2 {
 3 private:
 4     int _id;
 5     long _data;
 6     string _str;
 7 
 8 public:
 9     Foo():_id(0)
10     {
11         cout << "default ctor.this=" << this << " id=" << _id << endl;
12     }
13     Foo(int a):_id(a)
14     {
15         cout << "ctor.this=" << this << " id=" << _id << endl;
16     }
17 
18     virtual
19     ~Foo()
20     {
21         cout << "dtor.this=" << this << " id=" << _id << endl;
22     }
23 
24     //申請記憶體的函數必須是靜態的 調用這個函數時一般都是正在創建這個對象
25     //所以當調用時,這個對象還不存在,需要聲明成靜態
26     static void* operator new(size_t size);
27     static void  operator delete(void* pdead, size_t size);
28     static void* operator new[](size_t size);
29     static void  operator delete[](void* pdead, size_t size);
30 };
31 
32 void* Foo::operator new(size_t size)
33 {
34     Foo* p = (Foo*)malloc(size);
35     cout <<"operator new().size="<< size << "       return=" << p <<endl;
36     return p; //p 為記憶體起始點
37 }
38 
39 void Foo::operator delete(void* pdead, size_t size)//pdead 刪除點,和上面的p為同一個位置,size 為將要刪除的記憶體大小
40 {
41     cout <<"operator delete.pdead=" << pdead << "       size=" << size <<endl;
42     cout << endl;
43     free(pdead);
44 }
45 
46 void* Foo::operator new[](size_t size)
47 {
48     Foo* p = (Foo*)malloc(size);
49     cout <<"operator new[].size="<< size <<"     return=" << p << endl;
50     return p;
51 }
52 
53 void Foo::operator delete[](void* pdead, size_t size)
54 {
55     cout<< "operator delete[].pdead=" << pdead << "     size="<< size <<endl;
56     cout << endl;
57     free(pdead);
58 }
class Foo

  測試函數

1 void testoperatornew()
2 {
3     cout << "sizeof(Foo)="<<sizeof(Foo) << endl;
4     Foo* p = new Foo(7);
5     delete p;
6 
7     Foo* pArray = new Foo[5];
8     delete [] pArray;
9 }
View Code

  執行結果

image

重載new(),delete()

    可以重載class member operator new(), 其中第一個參數必須是size_t,其餘參數以new所指定的placement arguments 為初值,出現於new() 括弧內的就是所謂的placems arguments.

     也可以重載class member operator delete(),但他們不會被delete調用,只有當new所調用的ctor拋出異常,才會調用重載版的operator delete(),主要用來釋放未完全創建成功的object所占有的記憶體。

示例代碼

 1 class Foo2
 2 {
 3 private:
 4     int _id;
 5 
 6 public:
 7     Foo2()
 8     {
 9         cout << " Foo2()::Foo2() "  << endl;
10     }
11     Foo2(int a)
12     {
13         cout << "Foo2()::Foo2(int) "<< endl;
14         throw Bad();
15     }
16 
17 
18     void* operator new(size_t size)
19     {
20         cout << "operator new(size_t size),  size="<< size <<endl;
21         return malloc(size);
22     }
23 
24     // 標準庫提供的placeent new()的重載形式
25     void* operator new(size_t size, void* star)
26     {
27         cout << "operator new(size_t size),  size="<< size<< " star = " << star <<endl;
28         return malloc(size);
29     }
30     // 模擬標準庫的形式,只傳回pointer
31     void* operator new(size_t size, long extra)
32     {
33         cout << "operator new(size_t size, long extra),  size="<< size <<" extra = " << extra<<endl;
34         return malloc(size+extra);
35     }
36 
37     void* operator new(size_t size, long extra,char init)
38     {
39         cout << "operator new(size_t size, long extra,char init),  size="<< size <<" extra = " << extra
40              <<" init = " << init<<endl;
41         return malloc(size+extra);
42     }
43     /* 又一個 ,但故意寫錯第一參數類型
44       void* operator new(long extra, char init) //error: 'operator new' takes type 'size_t' ('unsigned int')
45     {
46         return malloc(extra);                   //as first parameter [-fpermissive]|
47     } */
48 
49     void operator delete(void*,long)
50     {
51         cout<<" operator delete(void*,size_t) " <<endl;
52     }
53     void operator delete(void*,long,char)
54     {
55         cout<<" operator delete(void*,long,char) " <<endl;
56     }
57 };
58 
59 void testFoo2()
60 {
61     Foo2 start;
62     Foo2* p1= new Foo2;
63     Foo2* p2= new(&start) Foo2;
64     Foo2* p3= new(100) Foo2;
65     Foo2* p4= new(100,'A') Foo2;
66     Foo2* p5= new(100) Foo2(1);
67     Foo2* p6= new(100,'A') Foo2(1);
68     Foo2* p7= new(&start) Foo2(1);
69     Foo2* p8= new Foo2(1);
70 }
View Code

運行結果

image五、記憶體池

記憶體池的優點

1.減少malloc的使用,提高運行效率

2.減少記憶體碎片,減少cookie

構造簡單的記憶體池,示例代碼如下

  1 #include<iostream>
  2 #include <stdlib.h>
  3 #include<complex>
  4 #include <memory>    //內含 std::allocator
  5 #include <ext\pool_allocator.h> // __pool_alloc
  6 
  7 using namespace std;
  8 
  9 class Screen
 10 {
 11 public:
 12     Screen(int x): i(x)  { }
 13     int get() {    return i; }
 14 /************************重載 ××××*************/
 15     void* operator new(size_t);
 16     void operator delete(void*, size_t);
 17 /* **********************重載 ××××************ ***/
 18 private:
 19     Screen* next;//4bit
 20     static Screen* freeStore;
 21     static const int screenChunk;//想要創建多少組
 22 
 23 private:
 24     int i; //4bit
 25 };
 26 
 27 Screen* Screen::freeStore = 0;
 28 const int Screen::screenChunk = 24;
 29 
 30 /************************重載×××××××××××××××××××××××××*************/
 31 
 32 void* Screen::operator new(size_t size)
 33 {
 34     Screen* p;
 35     if(!freeStore)
 36     {
 37         //linked list是空的,所以申請一大塊記憶體
 38         size_t chunk = screenChunk * size; //192 Screen的記憶體大小為8共24組  24 * 8 = 192
 39         freeStore = p =
 40                 reinterpret_cast<Screen*>(new char[chunk]);
 41         cout << "startPisotion: " << p << endl;
 42 
 43         //將一大塊記憶體分割成片段,當做linked list串接起來
 44         for(; p != &freeStore[screenChunk-1]; ++p)
 45         {
 46             p->next = p+1;
 47         }
 48         p->next = 0;
 49     }
 50     p = freeStore;
 51     freeStore = freeStore->next;
 52 
 53     return p;
 54 }
 55 
 56 void Screen::operator delete(void* p, size_t)
 57 {
 58     //將delete object插回 free list前端
 59     (static_cast<Screen*>(p)) -> next = freeStore;
 60     freeStore = static_cast<Screen*>(p);
 61 }
 62 /************************重載×××××××××××××××××××××××××**************/
 63 void test_per()
 64 {
 65     cout << "sizeof(int)"<< sizeof(int*) << endl;
 66     cout << "sizeof(Screen*)"<< sizeof(Screen*) << endl;
 67     cout << "sizeof(Screen)"<< sizeof(Screen) << endl;
 68 
 69     size_t const N = 10;
 70 
 71     Screen* p[N];
 72 
 73     cout << "overload operator new" << endl;
 74     for(int i=0; i<N; i++)
 75     {
 76         p[i] = new Screen(i);
 77     }
 78 
 79     for(int i = 0; i<10; i++)
 80     {
 81         cout << p[i] << endl;//輸出每個Screen的記憶體起點
 82     }
 83 
 84     for(int i=0; i<N; i++)
 85     {
 86         delete p[i];
 87     }
 88 
 89     cout << "glob operator new" << endl;
 90 
 91     Screen* q[N];
 92 
 93     for(int i=0; i<N; i++)
 94     {
 95         q[i] = ::new Screen(i);
 96     }
 97 
 98     for(int i = 0; i<10; i++)
 99     {
100         cout << q[i] << endl;
101     }
102 
103     for(int i=0; i<N; i++)
104     {
105         ::delete q[i];
106     }
107 }
108 
109 int main()
110 {
111     test_per();
112     return 0;
113 }
View Code

測試結果

image image

  左邊是重載了member operator new/delete 的結果,右邊是沒有使用重載而是使用global operator new/delete 的結果。class Screen 大小為8位元組,使用重載的函數數組內相鄰元素地址相隔8個位元組,減少了使用malloc時的cookie;而不使用重載函數,相隔48個位元組。

六、static allocate

上節分配記憶體的方法,每個類中都要重載new,delete; 因此將該部分提取出來,封裝為一個類allocate; 將應用類的實現與記憶體分配細節分離開來

測試代碼

  1 #include<iostream>
  2 #include <stdlib.h>
  3 #include<complex>
  4 //#include <memory>    //內含 std::allocator
  5 //#include <ext\pool_allocator.h> // __pool_alloc
  6 
  7 using namespace  std ;
  8 
  9 class myAllocator
 10 {
 11 private:
 12     struct obj
 13     {
 14         struct obj* next;
 15     };
 16 
 17 public:
 18     void* allocate(size_t);
 19     void  deallocate(void*, size_t);
 20 private:
 21     obj* freeStore = nullptr;
 22     const int CHUNK = 5; // 便於觀察,設為5
 23 };
 24 
 25 void myAllocator::deallocate(void* p, size_t size)
 26 {
 27     cout << "myAllocator::deallocate" << "size: " << size <<endl;
 28     ((obj*)p)->next = freeStore;
 29     freeStore = (obj*)p;
 30 }
 31 
 32 void* myAllocator::allocate(size_t size)
 33 {
 34    // cout << "myAllocator::allocate" << "size: " << size <<endl;
 35     obj* p;
 36     if(!freeStore)
 37     {
 38         size_t chunk = CHUNK * size;


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • uWSGI伺服器遇到invalid request block size錯誤解決方法 ...
  • #server 同時進行了md5加密 1 import socket 2 import hashlib 3 4 client = socket.socket() 5 client.connect(('localhost',9998)) 6 while True: 7 cmd = input("請輸入... ...
  • 微信最新的小程式裡面出了個叫“跳一跳”的小游戲,一經推出立馬刷爆了朋友圈,而一些大神們也通過Python實現了自動玩游戲具體代碼見(Github地址:https://github.com/wangshub/wechat_jump_game)。我也通過一些研究成功搭建了這個程式運行的環境,在朋友圈小刷 ...
  • Dict 1 使用鍵-值(key-value)存儲,具有極快的查找速度。 2 eg:d = {'Michael': 95, 'Bob': 75, 'Tracy': 85} >>> d['Michael'] 95 3 要避免key不存在的錯誤,有兩種辦法,一是通過in判斷key是否存在: 二是通過di ...
  • 工作中,需要處理與另一方系統數據交換的問題,採用的是調用遠程介面的方法,數據格式選擇的是json,今天就來聊一聊json,主要分析json數據和java Bean之間的轉換問題。 一、json是什麼 json,全稱是JavaScript Object Notation,中文翻譯是JS對象標記語言,是 ...
  • 官方描述Python is powerful... and fast; plays well with others; runs everywhere; is friendly & easy to learn; is Open.Python是一個易於學習且功能強大的編程語言.他具有高效率的數據結構,... ...
  • 如何在 Linux 上安裝 Nginx 1、下載 nginx 鏈接 : https://pan.baidu.com/s/1sll0Hrf 密碼 : xnem 2、終端依次執行下麵命令 3、解壓 4、進入解壓後的目錄 5、使用 configure 命令創建一 Makefile 文件 ( 直接在終端中輸 ...
  • Spring整合Hibernate Spring的Web項目中,web.xml文件會自動載入,以出現歡迎首頁。也可以在這個文件中對Spring的配置文件進行監聽,自啟動配置文件, 以及之前的整合Struts2,放置過濾器 在Spring的核心配置文件中,進行資料庫連接池配置,建立sessionFac ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...