【c++內存系列】內存管理面試版

本文轉自:https://blog.csdn.net/qq_34796146/article/details/104139121?depth_1-utm_source=distribute.pc_feed.150836&utm_source=distribute.pc_feed.150836

剛好是對前四篇內存教程的總結

一、C++內存管理詳解

1、內存的分配方式
(a) 棧:編譯器分配的內存,用來存儲函數的局部變量,函數調用結束後則自動釋放內存。
(b) 堆:程序員用new分配的內存,一般存儲指針;如果程序運行結束的時候沒有被釋放,則操作系統會自動回收。
(c) 自由存儲區:程序員用malloc分配的內存,使用free來釋放內存。
(d)全局/靜態存儲區:此內存區用來存放全局變量和static定義的靜態數據成員或者函數,這一塊內存是供程序共享的一塊內存。
(e) 常量存儲區:用來存放常量,不允許更改。
舉例:int* p = new int[5];
此語句在內存中的存儲情況如下:

å¨è¿éæå¥å¾çæè¿°

分析:以上C++語句實際到了堆和棧的概念,因爲指針p是用new來分配內存的,則p應該在堆區,而int[5]是局部變量,則應該存放在棧區。所以刪除內存是delete []p;而不是delete p;。

2、堆和棧的區別
答:
(a) 管理方式:棧的管理是編譯器來進行分配和管理,堆的管理一般是程序員通過new和delete來對內存進行分配和釋放。
(b) 空間大小:堆在系統中一般可以分配幾個G大小的內存,而棧一般分配幾M大小的內存。
(c) 碎片問題:堆在不斷的執行new和delete操作中,內存逐漸被碎片化,使得程序的執行效率變低;棧採用後進先出的策略,所以不會出現碎片化的問題。
(d) 增長方向:堆的增長方向是向着地址增大的方向進行的,而棧採用的是後進先出的策略,所以元素的增長方向是朝着地址減少的方向進行的。
(e) 分配方式:堆一般是動態分配的;而棧既有動態分配的方式也有靜態分配的方式,靜態分配使用alloc函數執行,也是由編譯器分配,動態由編譯器分配。
(f) 分配效率:一般情況下,棧的分配效率高於堆,因爲棧分配的時候由編譯器和操作系統底層將臨時變量和相關地址放在特定的寄存器中,讀寫效率高,而堆有上層複雜的數據結構會造成效率低的問題。
PS:若是需要佔用大塊內存還是分配給堆比較好。需要注意棧和堆的越界問題,會造成內存泄露。

3、控制C++的內存分配
答:因爲C++分配內存的過程特別簡單,程序員過度的使用new和delete會造成內存中堆破裂的問題。
解決方案:從不同固定大小的內存池中分配不同類型的對象。
實踐方法:對每個類重載new和delete就提供了這種控制。

void *operator new(size_t size)
{
    void *p=malloc(size);
    return (p);
}
void operator delete(void *p)
{
    free(p);
}


如果需要創建對象數組,也就是同時創建說個對象,調用系統的new[]和delete[]操作,則會調用系統的內存,同樣會出現以上的問題,所以也需要重載new[]和delete[]操作符。

class TestClass{
    public: 
        void *operator new[](size_t size);
        void operator delete[](void *p);
        //其他成員
};
void *Testclass::operator new[](size_t size)
{
    void *p = malloc(size);
    return (p);
}
void *Testclass::operator delete[](void *p)
{
    free(p);
}


4、C++內存分配可能出現的問題
答:
(a) 內存分配未成功,卻使用了它。原因:系統每次分配內存不一定會成功,如果使用了它,則會造成不可預計的錯誤。
(b) 內存分配雖然成功,但是尚未初始化就使用了它。原因:開發人員沒有初始化的觀念;誤認爲內存的缺省時初值爲0,導致引用初值錯誤。
(c) 內存分配成功並且已經初始化,但操作越過了內存邊界,比如數組越界。
(d) 內存分配成功後,忘記釋放了內存,造成內存泄漏。
(e) 釋放了內存卻還使用了它,原因:一是,程序對象的調用過程過於複雜,難以搞清對象是否釋放了內存;二是,返回了棧內存中的指針和引用,因爲該內存在函數體結束時自動銷燬;三是,使用free/delete釋放內存,沒有將指針設置爲NULL,導致產生野指針。
防止以上問題,採用以下的解決方案:
a.用malloc或new申請內存之後,應該立即檢查指針值是否爲NULL,防止使用指針值爲NULL的內存。如果指針p是函數的參數,那麼在函數的入口處assert(p!=NULL);如果是使用malloc或者new來申請內存,用if(p==NULL)。
b.不要忘記爲數組和動態內存賦初值,防止將未被初始化的內存作爲右值使用。
c.避免數組或者指針的下標越界。
d.動態內存的申請與釋放必須配對,防止內存泄露。
e.用free或delete釋放了內存之後,立即將指針設置爲NULL,防止產生野指針。

5、指針與數組之間的對比
答:數組在靜態存儲區或者棧上被創建,數組名對應着一塊內存,其地址與容量在生命週期內保持不變,只有數組的內容可以改變。指針可以隨時指向任意類型的內存塊,是可以隨時改變的。指針比數組靈活,但是也更加危險。

6、數組和指針容易發生的錯誤
答:char *p = "world";
編譯器會給出錯誤,const char* 實體“world”不能初始化char*。所以更不能用p[0]='x';來修改內容。
不能用數組名進行比較和賦值,需要藉助strcpy函數和strcmp函數。同時還有需要注意的一點是當數組數組作爲函數的參數進行傳遞的時候,該數組自動退化爲同類型的指針。實例代碼如下:

#include <iostream>
using namespace std;
void Func(char a[100]);

int main()
{
    char a[] = "Hello World";
    char b[100]="ddddd";
    cout << "通過strcpy將數組a[]的指針a賦值給數組b[]的指針" << endl;
    cout<<strcpy(b, a)<<endl;//p=a
    cout << "通過strcmp對a指針指向的數組與b指針指向的數組進行比較" << endl;
    cout<<strcmp(b,a)<<endl;//
    cout << b << endl;
    char* p = (char*)malloc(sizeof(char) * (strlen(a) + 1));
    cout << "將數組a的內容賦值給指針p" << endl;
    cout<<strcpy(p,a)<<endl;
    cout << "將指針p的內容與數組a[]進行比較" << endl;
    cout << strcmp(p,a) << endl;
    cout << "將數組作爲參數傳遞給函數" << endl;
    Func(a);
    free(p);
    p = NULL;
    return 0;
}

void Func(char a[100])
{
    cout << "a的內容" << endl;
    cout << a << endl;
    cout << "指針a的大小" << endl;
    cout << sizeof(a) << endl;
    cout << "指針*a所指向的內容" << endl;
    cout << *a << endl;
}

å¨è¿éæå¥å¾çæè¿°

7、指針參數是如何傳遞內存的
答:如果函數的參數是一個指針,不能指望用該指針去申請動態內存。原因:編譯器會爲了每個函數的參數分配一個臨時副本,指針參數p的副本是_p,編譯器使_p=p,當你分配內存的時候,爲_p分配了一塊內存,但是指針p卻沒有指向這塊內存,所以函數不會分配到內存。
以下爲錯誤代碼:

å¨è¿éæå¥å¾çæè¿°

正確代碼:

å¨è¿éæå¥å¾çæè¿°

8、什麼是野指針,如何預防
答:野指針不是NULL指針,是指向垃圾內存的指針。而且使用if(p==NULL)是判斷不了野指針的。
原因:a.定義指針的時候沒有初始化。它的缺省值會隨機指向內存中的一個地址,這是相當危險的。解決辦法如下:

    char* p = NULL;
    char* str = (char*)malloc(100);


b.指針p被free或者delete之後,沒有置爲NULL,讓人認爲還是一個合法的指針。
c.指針的操作超越了變量的作用域範圍。

class A
    {
    public:
        void Func(void) { cout << "Func of Class A" << endl; };
    };

    void Test(void)
    {
        A* p;
        {
            A a;
            p = &a;
        }//到這裏指針p已經被A的默認析構函數釋放了
        p->Func();//p是野指針
    }


9、malloc/free和new/delete的區別和聯繫?
答:malloc/free是系統的庫函數,new/delete是操作符,其中編譯器可以管理new/delete而不能直接管理malloc、free。所以一個類中的構造函數和析構函數是使用new和delete這樣操作時比較方便的。

    class Obj
    {
    public:
        Obj(void) { cout << "Initialization" << endl; }
        ~Obj(void) { cout << "Destroy" << endl; }
        void Initialize(void) { cout << "Initialization" << endl; }
        void Destroy(void) { cout << "Destroy" << endl; }
    };

    void UseMallocFree(void)
    {
        Obj* a = (obj*)malloc(sizeof(obj)); // 申請動態內存
        a->Initialize(); // 初始化
        //…
        a->Destroy(); // 清除工作
        free(a); // 釋放內存
    }

    void UseNewDelete(void)
    {
        Obj* a = new Obj; // 申請動態內存並且初始化
        //…
        delete a; // 清除並且釋放內存
    }

補充:malloc/free要點,malloc函數返回的是void類型指針所以需要類型轉換,內存大小是sizeof(char)等等來判斷大小。
new/delete要點,new和delete函數是可以初始化的,如obj o = new obj(1);

10、內存耗盡怎麼辦?
答:內存耗盡會返回NULL,使用if判斷使用return或者exit(-1)來返回或者終止程序,推薦使用exit(-1);

後續待補充。。。。


————————————————
版權聲明:本文爲CSDN博主「知愚」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_34796146/article/details/104139121

 

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