千萬不要錯過的後端【純乾貨】面試知識點整理 I I

千萬不要錯過的後端【純乾貨】面試知識點整理 I I

c++內存管理

上次分享整理的面試知識點 I , 今天咱們來繼續分享面試知識點整理 IIlinux

linux kernel 內核空間、內存管理、進程管理設備、驅動虛擬文件系統(vfs) 內核空間是受保護的,用戶不能對內核空間讀寫,不然會出現段錯誤
環境變量(env) PATH
命令行參數 char *agrv[]
棧區⬇️ 函數的返回地址,返回值,參數,局部變量
共享庫(映射區)⬇️ 調用動態庫,或者mmap函數進行文件映射
堆區⬆️ 用new/malloc申請的內存,同時須要適用delete/free來釋放採用鏈式儲存結構
.bss區 未初始化的全局變量和靜態變量以及 初始化爲 0 的 全局變量和靜態變量編譯時就已經分配了空間
.data區 已初始化的全局變量和靜態變量編譯時就已經分配了空間
.text 一、只讀存儲區 -- 常量,const全局變量二、文本區 -- 程序代碼,機器代碼
0-4k保護區
#include<stdio.h>
 
int a;    //未初始化全局區 .bss
int b=1;    //已初始化全局區  .data
static int c=2;    //已初始化全局區  .data
const int d=3;    //只讀數據段,也叫文字常量區 ro.data, d的值不能被修改
int main(void)
{
    int e=4;    //棧區
    static int f=5;    //已初始化全局區
    const int g=6;    //棧區,不能經過變量名修改其值,但可經過其地址修改其值
    int *p=malloc(sizeof(int))    //指針變量p在棧區,但其所指向的4字節空間在堆區
    char *str="abcd";    //字符串「abcd」存在文字常量區,指針變量str在棧區,存的是「abcd」的起始地址
    return 0;
}

內存泄露及分類

img

內存泄漏,是因爲疏忽或錯誤形成程序未能釋放掉再也不使用的內存。內存泄漏,並非指內存內存再物理地址上的消失,而是應用程序分配某段內存後,失去了對該段內存的控制,於是形成內存的浪費。ios

  • 通常狀況是new/malloc 後,沒有及時delete/free釋放內存,判斷爲內存泄露
  • linux中可使用valgrind來檢測內存泄漏

內存泄漏的分類:

  • 堆內存泄漏 --- new/malloc 後 沒有delete/free掉
  • 系統資源泄漏 --- 系統分配的資源,沒有用指定的函數釋放掉,致使系統資源的浪費,嚴重影響系統性能,如:socket,bitmap,handle
  • 沒有將父類的析構函數定義爲虛函數 --- 父類指針指向子類對象的時候,釋放內存的時候,若父類的析構函數不是virtual的話,子類的內存是不會獲得釋放的,所以會內存泄漏

c++中是如何處理內存泄漏的:

使用valgrind,mtrace來檢測內存泄漏c++

避免內存泄漏:git

1.事前預防型。如智能指針等。 2.過後查錯型。如泄漏檢測工具。github

智能指針

使用智能指針,智能指針會自動刪除被分配的內存,他和普通指針相似,只是不須要手動釋放指針,智能指針本身管理內存釋放,不用擔憂內存泄漏問題面試

智能指針有:編程

  • auto_ptr
  • unique_ptr
  • shared_ptr
  • weak_ptr

其中auto_ptr c++11已經被棄用了後端

unique_ptr

獨佔的智能指針,只能有一個對象擁有全部權,獨佔指針的是本身管理內存的,指針存在於棧空間,開闢的內存在堆空間,這裏的堆空間是和智能指針綁定的,智能指針隨着函數結束被銷燬以前,智能指針會先去把堆裏面的內存銷燬api

其中涉及數組

  • move函數 -- 可使用move函數來轉移全部權,轉移全部權後,原來的指針就無權訪問
  • reset函數 -- 能夠用reset函數來重置全部權,會把以前的對象全部權釋放掉,從新建立一個全部權對象
  • make_unique -- 快速的建立一個unique_ptr智能指針的對象 如 auto myptr = make_unique<person>();

若是但願只有一個智能指針管理資源 就使用 unique_ptr

#include <iostream>
#include <string>
#include <memory>

using namespace std;

struct person
{
    ~person()
    {
        cout<<"~person"<<endl;
    }
    string str;
};

unique_ptr<person> test()
{
    return unique_ptr<person> (new person);
}

int main()
{
    //unique_ptr is ownership 
    unique_ptr<person> p = test();
    p->str = "hello world";
    
    unique_ptr<person> p2 = move(p);  //可使用move函數來轉移全部權,轉移全部權後,原來的指針就無權訪問
    if(!p)
    {
        cout<<"p == null" <<endl;
    }

    if(p2)
    {
        cout<<"p2 have ownership"<<endl;
        cout<<p2->str<<endl;
    }

    p2.reset(new person);//能夠用reset函數來重置全部權,會把以前的對象全部權釋放掉,從新建立一個全部權對象
    if(p2->str.empty())
    {
        cout<<"str is null"<<endl;
    }
 
     return 0;
 }

shared_ptr

共享的智能指針,shared_ptr使用引用計數(use_count方法),每一個shared_ptr的拷貝都指向同一塊內存,在最後一個shared_ptr被析構的時候,內存纔會被釋放

  • shared_ptr 是引用計數的方式,使用use_count查看計數
  • make_shared 快捷建立 shared_ptr

使用函數返回本身的shared_ptr時,須要繼承enable_shared_from_this類,使用shared_from_this函數進行返回

注意事項:

  • 不要將this指針做爲返回值
  • 要避免循環引用
  • 不要再函數實參種建立shared_ptr,在調用函數以前先定義以及初始化它
  • 不要用一個原始指針初始化多個shared_ptr

但願多個指針管理同一個資源就使用shared_ptr

#include <iostream>
#include <string>
#include <memory>

using namespace std;

struct person
    :enable_shared_from_this<person>{
    string str;
    void show()
    {
        cout<<str<<endl;
    }
    ~person()
    {
        cout<<"~person"<<endl;
    }
    shared_ptr<person> getshared()
    {
        return shared_from_this();
    }
};

int main()
{
    #if 0
    shared_ptr<person> ptr(new person);
    cout<< ptr.use_count()<<endl;
    
    shared_ptr<person> ptr2 = ptr;
    cout<< ptr.use_count()<<endl;

    shared_ptr<person> a = make_shared<person>();
    cout<< a.use_count()<<endl;
    a = ptr2;
    cout<< ptr.use_count()<<endl;


    shared_ptr<person> mm = a->getshared();
    
    #endif

    shared_ptr<person> ptr;
    {
        shared_ptr<person> ptr2(new person);
        ptr2->str = "hello";
        
        ptr = ptr2->getshared();    
        cout<< ptr.use_count()<<endl;
    }
    ptr->show();

    return 0;
}

weak_ptr

弱引用的智能指針

是用來監視shared_ptr的,不會使用計數器加1,也不會使用計數器減1,主要是爲了監視shared_ptr的生命週期,更像是shared_ptr的一個助手。weak_ptr還能夠用來返回this指針和解決循環引用的問題。

shared_ptr會有循環引用的問題 ,解決方式爲 把類中的shared_ptr 換成 weak_ptr便可

struct ListNode
{
    std::shared_ptr<ListNode> _next;//std::weak_ptr<ListNode> _next; 就能夠解決
    std::shared_ptr<ListNode>  _prev;//std::weak_ptr<ListNode> _pre; 就能夠解決
    
    ~ListNode()
    {
        cout << "~ListNode()" << endl;
    }
};
void test_shared_ptr_cycleRef()
{
    std::shared_ptr<ListNode> cur(new ListNode);
    std::shared_ptr<ListNode> next(new ListNode);
 
    cur->_next = next;
    next->_prev = cur;
 
}
int main()
{
    test_shared_ptr_cycleRef();
    system("pause");
    return 0;
}

例如上述代碼案例

void shared_ptr_cycleRef(){
    std::shared_ptr<LISTNODE> cur LISTNODE;
    std::shared_ptr<LISTNODE> next LISTNODE;

     cur->_next = next;
     next->_pre = cur;
}

Cur 和 next 存在循環引用,他們的引用計數都變爲 2

出了做用域以後,cur 和 next 被銷燬,引用計數減 1

所以要釋放cur , 就須要釋放next 的 _pre,要釋放next , 就須要釋放cur 的 _next

內存泄漏檢測工具

valgrind內存檢測工具

valgrind的官方網址是:http://valgrind.org

valgrind被設計成非侵入式的,它直接工做於可執行文件上,所以在檢查前不須要從新編譯、鏈接和修改你的程序。要檢查一個程序很簡單

命令以下: valgrind --tool=tool_name program_name

  • 作內存檢查: valgrind --tool=memcheck ls -l
  • 檢查內存泄漏: valgrind --tool=memcheck --leak-check=yes ls -l

valgrind有以下幾個工具

memcheck

memcheck 探測程序中內存管理存在的問題。

它檢查全部對內存的讀/寫操做,並截取全部的malloc/new/free/delete調用。所以memcheck工具可以探測到如下問題:

Memcheck 工具主要檢查下面的程序錯誤:

  • 使用未初始化的內存 (Use of uninitialised memory)
  • 使用已經釋放了的內存 (Reading/writing memory after it has been free’d)
  • 使用超過 malloc分配的內存空間(Reading/writing off the end of malloc’d blocks)
  • 對堆棧的非法訪問 (Reading/writing inappropriate areas on the stack)
  • 申請的空間已經釋放釋放,即內存泄漏 (Memory leaks – where pointers to malloc’d blocks are lost forever)
  • malloc/free/new/delete申請和釋放內存的匹配(Mismatched use of malloc/new/new [] vs free/delete/delete [])
  • src和dst的重疊(Overlapping src and dst pointers in memcpy() and related functions)

cachegrind

cachegrind 是一個cache剖析器。

它模擬執行CPU中的L1, D1和L2 cache,

所以它能很精確的指出代碼中的cache未命中。

它能夠打印出cache未命中的次數,內存引用和發生cache未命中的每一行 代碼,每個函數,每個模塊和整個程序的摘要。

若要求更細緻的信息,它能夠打印出每一行機器碼的未命中次數。

在x86和amd64上, cachegrind經過CPUID自動探測機器的cache配置,因此在多數狀況下它再也不須要更多的配置信息了。

helgrind

helgrind查找多線程程序中的競爭數據。

helgrind查找內存地址,那些被多於一條線程訪問的內存地址,可是沒有使用一致的鎖就會被查出。這表示這些地址在多線程間訪問的時候沒有進行同步,極可能會引發很難查找的時序問題。

產生段錯誤的緣由

  • 使用野指針
  • 試圖對字符串常量進行修改

new和malloc的區別:

在申請內存時

  • new是一個操做符,能夠被重載,malloc是一個庫函數
  • new在申請內存的時候,會按照對象的數據結構分配內存,malloc分配指定的內存大小
  • new申請內存時,會調用構造函數,malloc不會
  • new申請內存時,返回對象的指針,malloc申請內存的時候,返回(void *) 所以須要強轉
  • 申請數組的時候,new[],會一次性分配全部內存,調用多個構造函數,所以須要delete[]來銷燬內存,調用屢次析構函數,而 malloc 只能sizeof(int)*n
  • new申請內存失敗,會拋bac_malloc異常, malloc申請失敗則返回NULL
  • malloc當分配的內存不夠的時候,會使用realloc再次分配內存, new沒有這樣的機制。
  • new分配的內存須要用delete釋放,delete 會調用析構函數,malloc分配的內存須要free 函數釋放

realloc的原理:

realloc是在C語言中出現的,c++已經摒棄realloc函數,realloc函數分配一塊新內存的時候,會把原內存中的內存copy到新內存中,經過memmove的方式

共享內存相關的api

  • shmget 新建共享內存
  • shmat 鏈接共享內存到當前地址空間
  • shmdt 分離共享內存
  • shmctl 控制共享內存

c++ STL內存優化

c++11新特性:

關鍵字和語法

  • auto關鍵字

編譯器能夠根據初始化來推導數據類型,不能用於函數傳參和以及數組類型推導

  • nullptr關鍵字

一種特殊類型的字面量,能夠被轉成任意的其餘類型

  • 初始化列表

初始化類的列表

  • 右值引用

能夠實現移動語義和完美轉發,消除兩個對象交互時沒必要要的拷貝,節省存儲資源,提升效率

新增容器

  • 新增STL array ,tuple、unordered_map,unordered_set

智能指針,內存管理

  • 智能指針

新增 shared_ptr、weak_ptr用於內存管理

多線程

  • atomic原子操做

用於多線程互斥

其餘

  • lamda表達式

能夠經過捕獲列表訪問上下文的數據

  • std::function std::bin d封裝可執行對象

防止頭文件重複引用:

#ifndef

做用:相同的兩個文件不會被重複包含。

優勢:

  • 受C/C++語言標準的支持,不受編譯器的限制。
  • 不只僅侷限於避免同一個文件被重複包含,也能避免內容徹底相同的兩個文件(或代碼片斷)被重複包含。

缺點:

  • 若是不一樣頭文件中的宏名剛好相同,可能就會致使你看到頭文件明明存在,編譯器卻說找不到聲明的狀況。
  • 因爲編譯器每次都須要打開頭文件才能斷定是否有重複定義,所以在編譯大型項目時,#ifndef會使得編譯時間相對較長。

pragma once

做用:物理上的同一個文件不會被重複包含。

優勢:

  • 避免#ifndef中由於宏名相同致使的問題。
  • 因爲編譯器不須要打開頭文件就能斷定是否有重複定義,所以在編譯大型項目時,比#ifndef更快。

缺點:

  • \#pragma once只針對同一文件有效,對相同的兩個文件(或代碼片斷)使用無效
  • \#pragma once不受一些較老版本的編譯器支持,一些支持了的編譯器又打算去掉它,因此它的兼容性可能不夠好。

繼承與組合

  • 繼承是面向對象三大基本特徵之一(繼承,封裝,多態),繼承就是子類繼承父類的特徵和行爲,使得子類對象(實例)具備父類的實例域和方法,或子類從父類繼承方法,使得子類具備父類相同的行爲,繼承強調的是is-a關係,是‘白盒式’的代碼複用
  • 組合是經過對現有對象進行拼裝即組合產生新的具備更復雜的功能,組合體現的是總體和部分,強調的是has-a的關係,是‘黑盒式’的代碼複用

繼承與組合使用場景

  • 邏輯上B 是A 的「一種」a kind of

繼承 (如 男人 繼承 人類)

  • 邏輯上A 是B 的「一部分」a part of

組合(如 組合 眼 耳 口 鼻 -> 頭)

繼承與組合區別

  • 在繼承中,父類的內部細節對子類可見,其代碼屬於白盒式的複用,調的是is-a的關係,關係在編譯期就肯定
  • 組合中,對象之間的內部細節不可見,其代碼屬於黑盒式複用。強調的是has-a的關係,關係通常在運行時肯定

繼承與組合優缺點

繼承

優勢:

  • 支持擴展,經過繼承父類實現,但會使系統結構較複雜
  • 易於修改被複用的代碼

缺點:

  • 代碼白盒複用,父類的實現細節暴露給子類,破壞了封裝性
  • 當父類的實現代碼修改時,可能使得子類也不得不修改,增長維護難度。
  • 子類缺少獨立性,依賴於父類,耦合度較高
  • 不支持動態拓展,在編譯期就決定了父類

組合

優勢:

  • 代碼黑盒複用,被包括的對象內部實現細節對外不可見,封裝性好。
  • 總體類與局部類之間鬆耦合,相互獨立。
  • 支持擴展
  • 每一個類只專一於一項任務
  • 支持動態擴展,可在運行時根據具體對象選擇不一樣類型的組合對象(擴展性比繼承好)

缺點:

  • 建立總體類對象時,須要建立全部局部類對象。致使系統對象不少。

函數指針的好處和做用:

好處:簡化結構和程序通用性的問題,也是實現面向對象編程的一種途徑

做用:

  • 實現面向對象編程中的多態性
  • 回調函數

inline函數與宏定義

inline函數是C++引入的機制,目的是解決使用宏定義的一些缺點。

爲何要引入內聯函數(內聯函數的做用)

用它替代宏定義,消除宏定義的缺點。

宏定義使用預處理器實現,作一些簡單的字符替換所以不能進行參數有效性的檢測。

  • inline 相比宏定義有哪些優越處
  • inline 函數代碼是被放到符號表中,使用時像宏同樣展開,沒有調用的開銷效率很高;
  • inline 函數是真正的函數,因此要進行一系列的數據類型檢查;
  • inline 函數做爲類的成員函數,可使用類的保護成員及私有成員;

inline函數使用的場合

  • 使用宏定義的地方均可以使用 inline 函數;
  • 做爲類成員接口函數來讀寫類的私有成員或者保護成員;

爲何不能把全部的函數寫成 inline 函數

  • 函數體內的代碼比較長,將致使內存消耗代價;
  • 函數體內有循環,函數執行時間要比函數調用開銷大;
  • 另外類的構造與析構函數不要寫成內聯函數。

內聯函數與宏定義區別

  • 內聯函數在編譯時展開,宏在預編譯時展開;
  • 內聯函數直接嵌入到目標代碼中,宏是簡單的作文本替換;
  • 內聯函數有類型檢測、語法判斷等功能,而宏沒有;
  • inline 函數是函數,宏不是;
  • 宏定義時要注意書寫(參數要括起來)不然容易出現歧義,內聯函數不會產生歧義;

總結

  • 分享了內存管理,內存泄露,智能指針
  • 內存泄露檢測工具
  • 代碼中產生段錯誤的緣由
  • 內存優化
  • 其他小知識點

歡迎點贊,關注,收藏

朋友們,你的支持和鼓勵,是我堅持分享,提升質量的動力

好了,本次就到這裏,下一次 GO的併發編程分享

技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

相關文章
相關標籤/搜索