一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

經(jīng)常遇到的導(dǎo)致內(nèi)存泄漏的原因

Linux愛好者 ? 來(lái)源:Linux愛好者 ? 作者:Linux愛好者 ? 2022-06-06 14:12 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

作為C/C++開發(fā)人員,內(nèi)存泄漏是最容易遇到的問題之一,這是由C/C++語(yǔ)言的特性引起的。C/C++語(yǔ)言與其他語(yǔ)言不同,需要開發(fā)者去申請(qǐng)和釋放內(nèi)存,即需要開發(fā)者去管理內(nèi)存,如果內(nèi)存使用不當(dāng),就容易造成段錯(cuò)誤(segment fault)或者內(nèi)存泄漏(memory leak)。

今天,借助此文,分析下項(xiàng)目中經(jīng)常遇到的導(dǎo)致內(nèi)存泄漏的原因,以及如何避免和定位內(nèi)存泄漏。

主要內(nèi)容如下:

ebc0584e-e54c-11ec-ba43-dac502259ad0.png

背景

C/C++語(yǔ)言中,內(nèi)存的分配與回收都是由開發(fā)人員在編寫代碼時(shí)主動(dòng)完成的,好處是內(nèi)存管理的開銷較小,程序擁有更高的執(zhí)行效率;弊端是依賴于開發(fā)者的水平,隨著代碼規(guī)模的擴(kuò)大,極容易遺漏釋放內(nèi)存的步驟,或者一些不規(guī)范的編程可能會(huì)使程序具有安全隱患。如果對(duì)內(nèi)存管理不當(dāng),可能導(dǎo)致程序中存在內(nèi)存缺陷,甚至?xí)谶\(yùn)行時(shí)產(chǎn)生內(nèi)存故障錯(cuò)誤。

內(nèi)存泄漏是各類缺陷中十分棘手的一種,對(duì)系統(tǒng)的穩(wěn)定運(yùn)行威脅較大。當(dāng)動(dòng)態(tài)分配的內(nèi)存在程序結(jié)束之前沒有被回收時(shí),則發(fā)生了內(nèi)存泄漏。由于系統(tǒng)軟件,如操作系統(tǒng)、編譯器、開發(fā)環(huán)境等都是由C/C++語(yǔ)言實(shí)現(xiàn)的,不可避免地存在內(nèi)存泄漏缺陷,特別是一些在服務(wù)器上長(zhǎng)期運(yùn)行的軟件,若存在內(nèi)存泄漏則會(huì)造成嚴(yán)重后果,例如性能下降、程序終止、系統(tǒng)崩潰、無(wú)法提供服務(wù)等。

所以,本文從原因、避免以及定位幾個(gè)方面去深入講解,希望能給大家?guī)?lái)幫助。

概念

內(nèi)存泄漏(Memory Leak)是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)崩潰等嚴(yán)重后果。

當(dāng)我們?cè)诔绦蛑袑?duì)原始指針(raw pointer)使用new操作符或者free函數(shù)的時(shí)候,實(shí)際上是在堆上為其分配內(nèi)存,這個(gè)內(nèi)存指的是RAM,而不是硬盤等永久存儲(chǔ)。持續(xù)申請(qǐng)而不釋放(或者少量釋放)內(nèi)存的應(yīng)用程序,最終因內(nèi)存耗盡導(dǎo)致OOM(out of memory)。

ebf8d7aa-e54c-11ec-ba43-dac502259ad0.png

方便大家理解內(nèi)存泄漏的危害,舉個(gè)簡(jiǎn)單的例子。有一個(gè)賓館,共有100間房間,顧客每次都是在前臺(tái)進(jìn)行登記,然后拿到房間鑰匙。如果有些顧客不需要該房間了,既不去前臺(tái)處登記退房,也不歸還鑰匙,久而久之,前臺(tái)處可用房間越來(lái)越少,收入也越來(lái)越少,瀕臨倒閉。

當(dāng)程序申請(qǐng)了內(nèi)存,而不進(jìn)行歸還,久而久之,可用內(nèi)存越來(lái)越少,OS就會(huì)進(jìn)行自我保護(hù),殺掉該進(jìn)程,這就是我們常說的OOM(out of memory)

分類

內(nèi)存泄漏分為以下兩類:

  • 堆內(nèi)存泄漏:我們經(jīng)常說的內(nèi)存泄漏就是堆內(nèi)存泄漏,在堆上申請(qǐng)了資源,在結(jié)束使用的時(shí)候,沒有釋放歸還給OS,從而導(dǎo)致該塊內(nèi)存永遠(yuǎn)不會(huì)被再次使用
  • 資源泄漏:通常指的是系統(tǒng)資源,比如socket,文件描述符等,因?yàn)檫@些在系統(tǒng)中都是有限制的,如果創(chuàng)建了而不歸還,久而久之,就會(huì)耗盡資源,導(dǎo)致其他程序不可用

本文主要分析堆內(nèi)存泄漏,所以后面的內(nèi)存泄漏均指的是堆內(nèi)存泄漏

根源

內(nèi)存泄漏,主要指的是在堆(heap)上申請(qǐng)的動(dòng)態(tài)內(nèi)存泄漏,或者說是指針指向的內(nèi)存塊忘了被釋放,導(dǎo)致該塊內(nèi)存不能再被申請(qǐng)重新使用。

之前在知乎上看了一句話,指針是C的精髓,也是初學(xué)者的一個(gè)坎。換句話說,內(nèi)存管理是C的精髓,C/C++可以直接跟OS打交道,從性能角度出發(fā),開發(fā)者可以根據(jù)自己的實(shí)際使用場(chǎng)景靈活進(jìn)行內(nèi)存分配和釋放。

雖然在C++中自C++11引入了smart pointer,雖然很大程度上能夠避免使用裸指針,但仍然不能完全避免,最重要的一個(gè)原因是你不能保證組內(nèi)其他人不適用指針,更不能保證合作部門不使用指針。

那么為什么C/C++中會(huì)存在指針呢?

這就得從進(jìn)程的內(nèi)存布局說起。

進(jìn)程內(nèi)存布局

ec2bffcc-e54c-11ec-ba43-dac502259ad0.png

上圖為32位進(jìn)程的內(nèi)存布局,從上圖中主要包含以下幾個(gè)塊:

  • 內(nèi)核空間:供內(nèi)核使用,存放的是內(nèi)核代碼和數(shù)據(jù)
  • stack:這就是我們經(jīng)常所說的棧,用來(lái)存儲(chǔ)自動(dòng)變量(automatic variable)
  • mmap:也成為內(nèi)存映射,用來(lái)在進(jìn)程虛擬內(nèi)存地址空間中分配地址空間,創(chuàng)建和物理內(nèi)存的映射關(guān)系
  • heap:就是我們常說的堆,動(dòng)態(tài)內(nèi)存的分配都是在堆上
  • bss:包含所有未初始化的全局和靜態(tài)變量,此段中的所有變量都由0或者空指針初始化,程序加載器在加載程序時(shí)為BSS段分配內(nèi)存
  • ds:初始化的數(shù)據(jù)塊
    • 包含顯式初始化的全局變量和靜態(tài)變量
    • 此段的大小由程序源代碼中值的大小決定,在運(yùn)行時(shí)不會(huì)更改
    • 它具有讀寫權(quán)限,因此可以在運(yùn)行時(shí)更改此段的變量值
    • 該段可進(jìn)一步分為初始化只讀區(qū)和初始化讀寫區(qū)
  • text:也稱為文本段
    • 該段包含已編譯程序的二進(jìn)制文件。
    • 該段是一個(gè)只讀段,用于防止程序被意外修改
    • 該段是可共享的,因此對(duì)于文本編輯器等頻繁執(zhí)行的程序,內(nèi)存中只需要一個(gè)副本

由于本文主要講內(nèi)存分配相關(guān),所以下面的內(nèi)容僅涉及到棧(stack)和堆(heap)。

ec7b00ae-e54c-11ec-ba43-dac502259ad0.png

棧一塊連續(xù)的內(nèi)存塊,棧上的內(nèi)存分配就是在這一塊連續(xù)內(nèi)存塊上進(jìn)行操作的。編譯器在編譯的時(shí)候,就已經(jīng)知道要分配的內(nèi)存大小,當(dāng)調(diào)用函數(shù)時(shí)候,其內(nèi)部的遍歷都會(huì)在棧上分配內(nèi)存;當(dāng)結(jié)束函數(shù)調(diào)用時(shí)候,內(nèi)部變量就會(huì)被釋放,進(jìn)而將內(nèi)存歸還給棧。

classObject{
public:
Object()=default;
//....
};

voidfun(){
Objectobj;

//dosth
}

在上述代碼中,obj就是在棧上進(jìn)行分配,當(dāng)出了fun作用域的時(shí)候,會(huì)自動(dòng)調(diào)用Object的析構(gòu)函數(shù)對(duì)其進(jìn)行釋放。

前面有提到,局部變量會(huì)在作用域(如函數(shù)作用域、塊作用域等)結(jié)束后析構(gòu)、釋放內(nèi)存。因?yàn)榉峙浜歪尫诺拇涡蚴莿偤猛耆喾吹?,所以可用到堆棧先進(jìn)后出(first-in-last-out, FILO)的特性,而 C++ 語(yǔ)言的實(shí)現(xiàn)一般也會(huì)使用到調(diào)用堆棧(call stack)來(lái)分配局部變量(但非標(biāo)準(zhǔn)的要求)。

因?yàn)闂I蟽?nèi)存分配和釋放,是一個(gè)進(jìn)棧和出棧的過程(對(duì)于編譯器只是一個(gè)指令),所以相比于堆上的內(nèi)存分配,棧要快的多。

雖然棧的訪問速度要快于堆,每個(gè)線程都有一個(gè)自己的棧,棧上的對(duì)象是不能跨線程訪問的,這就決定了??臻g大小是有限制的,如果??臻g過大,那么在大型程序中幾十乃至上百個(gè)線程,光??臻g就消耗了RAM,這就導(dǎo)致heap的可用空間變小,影響程序正常運(yùn)行。

設(shè)置

Linux系統(tǒng)上,可用通過如下命令來(lái)查看棧大?。?/p>

ulimit-s
10240

在筆者的機(jī)器上,執(zhí)行上述命令輸出結(jié)果是10240(KB)即10m,可以通過shell命令修改棧大小。

ulimit-s102400

通過如上命令,可以將??臻g臨時(shí)修改為100m,可以通過下面的命令:

/etc/security/limits.conf

分配方式

靜態(tài)分配

靜態(tài)分配由編譯器完成,假如局部變量以及函數(shù)參數(shù)等,都在編譯期就分配好了。

voidfun(){
inta[10];
}

上述代碼中,a占10 * sizeof(int)個(gè)字節(jié),在編譯的時(shí)候直接計(jì)算好了,運(yùn)行的時(shí)候,直接進(jìn)棧出棧。

動(dòng)態(tài)分配

可能很多人認(rèn)為只有堆上才會(huì)存在動(dòng)態(tài)分配,在棧上只可能是靜態(tài)分配。其實(shí),這個(gè)觀點(diǎn)是錯(cuò)的,棧上也支持動(dòng)態(tài)分配,該動(dòng)態(tài)分配由alloca()函數(shù)進(jìn)行分配。棧的動(dòng)態(tài)分配和堆是不同的,通過alloca()函數(shù)分配的內(nèi)存由編譯器進(jìn)行釋放,無(wú)需手動(dòng)操作。

特點(diǎn)

  • 分配速度快:分配大小由編譯器在編譯期完成
  • 不會(huì)產(chǎn)生內(nèi)存碎片:棧內(nèi)存分配是連續(xù)的,以FILO的方式進(jìn)棧和出棧
  • 大小受限:棧的大小依賴于操作系統(tǒng)
  • 訪問受限:只能在當(dāng)前函數(shù)或者作用域內(nèi)進(jìn)行訪問

堆(heap)是一種內(nèi)存管理方式。內(nèi)存管理對(duì)操作系統(tǒng)來(lái)說是一件非常復(fù)雜的事情,因?yàn)槭紫葍?nèi)存容量很大,其次就是內(nèi)存需求在時(shí)間和大小塊上沒有規(guī)律(操作系統(tǒng)上運(yùn)行著幾十甚至幾百個(gè)進(jìn)程,這些進(jìn)程可能隨時(shí)都會(huì)申請(qǐng)或者是釋放內(nèi)存,并且申請(qǐng)和釋放的內(nèi)存塊大小是隨意的)。

堆這種內(nèi)存管理方式的特點(diǎn)就是自由(隨時(shí)申請(qǐng)、隨時(shí)釋放、大小塊隨意)。堆內(nèi)存是操作系統(tǒng)劃歸給堆管理器(操作系統(tǒng)中的一段代碼,屬于操作系統(tǒng)的內(nèi)存管理單元)來(lái)管理的,堆管理器提供了對(duì)應(yīng)的接口_sbrk、_mmap等,只是該接口往往由運(yùn)行時(shí)庫(kù)(Linux為glibc)進(jìn)行調(diào)用,即也可以說由運(yùn)行時(shí)庫(kù)進(jìn)行堆內(nèi)存管理,運(yùn)行時(shí)庫(kù)提供了malloc/free函數(shù)由開發(fā)人員調(diào)用,進(jìn)而使用堆內(nèi)存。

分配方式

正如我們所理解的那樣,由于是在運(yùn)行期進(jìn)行內(nèi)存分配,分配的大小也在運(yùn)行期才會(huì)知道,所以堆只支持動(dòng)態(tài)分配,內(nèi)存申請(qǐng)和釋放的行為由開發(fā)者自行操作,這就很容易造成我們說的內(nèi)存泄漏。

特點(diǎn)

  • 變量可以在進(jìn)程范圍內(nèi)訪問,即進(jìn)程內(nèi)的所有線程都可以訪問該變量
  • 沒有內(nèi)存大小限制,這個(gè)其實(shí)是相對(duì)的,只是相對(duì)于棧大小來(lái)說沒有限制,其實(shí)最終還是受限于RAM
  • 相對(duì)棧來(lái)說訪問比較慢
  • 內(nèi)存碎片
  • 由開發(fā)者管理內(nèi)存,即內(nèi)存的申請(qǐng)和釋放都由開發(fā)人員來(lái)操作

堆與棧區(qū)別

理解堆和棧的區(qū)別,對(duì)我們開發(fā)過程中會(huì)非常有用,結(jié)合上面的內(nèi)容,總結(jié)下二者的區(qū)別。

對(duì)于棧來(lái)講,是由編譯器自動(dòng)管理,無(wú)需我們手工控制;對(duì)于堆來(lái)說,釋放工作由程序員控制,容易產(chǎn)生memory leak

  • 空間大小不同
    • 一般來(lái)講在 32 位系統(tǒng)下,堆內(nèi)存可以達(dá)到3G的空間,從這個(gè)角度來(lái)看堆內(nèi)存幾乎是沒有什么限制的。
    • 對(duì)于棧來(lái)講,一般都是有一定的空間大小的,一般依賴于操作系統(tǒng)(也可以人工設(shè)置)
  • 能否產(chǎn)生碎片不同
    • 對(duì)于堆來(lái)講,頻繁的內(nèi)存分配和釋放勢(shì)必會(huì)造成內(nèi)存空間的不連續(xù),從而造成大量的碎片,使程序效率降低。
    • 對(duì)于棧來(lái)講,內(nèi)存都是連續(xù)的,申請(qǐng)和釋放都是指令移動(dòng),類似于數(shù)據(jù)結(jié)構(gòu)中的進(jìn)棧和出棧
  • 增長(zhǎng)方向不同
    • 對(duì)于堆來(lái)講,生長(zhǎng)方向是向上的,也就是向著內(nèi)存地址增加的方向
    • 對(duì)于棧來(lái)講,它的生長(zhǎng)方向是向下的,是向著內(nèi)存地址減小的方向增長(zhǎng)
  • 分配方式不同
    • 堆都是動(dòng)態(tài)分配的,比如我們常見的malloc/new;而棧則有靜態(tài)分配和動(dòng)態(tài)分配兩種。
    • 靜態(tài)分配是編譯器完成的,比如局部變量的分配,而棧的動(dòng)態(tài)分配則通過alloca()函數(shù)完成
    • 二者動(dòng)態(tài)分配是不同的,棧的動(dòng)態(tài)分配的內(nèi)存由編譯器進(jìn)行釋放,而堆上的動(dòng)態(tài)分配的內(nèi)存則必須由開發(fā)人自行釋放
  • 分配效率不同
    • 棧有操作系統(tǒng)分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高
    • 堆內(nèi)存的申請(qǐng)和釋放專門有運(yùn)行時(shí)庫(kù)提供的函數(shù),里面涉及復(fù)雜的邏輯,申請(qǐng)和釋放效率低于棧

截止到這里,棧和堆的基本特性以及各自的優(yōu)缺點(diǎn)、使用場(chǎng)景已經(jīng)分析完成,在這里給開發(fā)者一個(gè)建議,能使用棧的時(shí)候,就盡量使用棧,一方面是因?yàn)樾矢哂诙?,另一方面?nèi)存的申請(qǐng)和釋放由編譯器完成,這樣就避免了很多問題。

擴(kuò)展

終于到了這一小節(jié),其實(shí),上面講的那么多,都是為這一小節(jié)做鋪墊。

在前面的內(nèi)容中,我們對(duì)比了棧和堆,雖然棧效率比較高,且不存在內(nèi)存泄漏、內(nèi)存碎片等,但是由于其本身的局限性(不能多線程、大小受限),所以在很多時(shí)候,還是需要在堆上進(jìn)行內(nèi)存。

我們先看一段代碼:

#include
#include

intmain(){
inta;
int*p;
p=(int*)malloc(sizeof(int));
free(p);

return0;
}

上述代碼很簡(jiǎn)單,有兩個(gè)變量a和p,類型分別為int和int *,其中,a和p存儲(chǔ)在棧上,p的值為在堆上的某塊地址(在上述代碼中,p的值為0x1c66010),上述代碼布局如下圖所示:

ec966650-e54c-11ec-ba43-dac502259ad0.png

產(chǎn)生方式

以產(chǎn)生的方式來(lái)分類,內(nèi)存泄漏可以分為四類:

  • 常發(fā)性內(nèi)存泄漏
  • 偶發(fā)性內(nèi)存泄漏
  • 一次性內(nèi)存泄漏
  • 隱式內(nèi)存泄漏

常發(fā)性內(nèi)存泄漏

產(chǎn)生內(nèi)存泄漏的代碼或者函數(shù)會(huì)被多次執(zhí)行到,在每次執(zhí)行的時(shí)候,都會(huì)產(chǎn)生內(nèi)存泄漏。

偶發(fā)性內(nèi)存泄漏

常發(fā)性內(nèi)存泄漏不同的是,偶發(fā)性內(nèi)存泄漏函數(shù)只在特定的場(chǎng)景下才會(huì)被執(zhí)行。

筆者在19年的時(shí)候,曾經(jīng)遇到一個(gè)這種內(nèi)存泄漏。有一個(gè)函數(shù)專門進(jìn)行價(jià)格加密,每次泄漏3個(gè)字節(jié),且只有在競(jìng)價(jià)成功的時(shí)候,才會(huì)調(diào)用此函數(shù)進(jìn)行價(jià)格加密,因此泄漏的非常不明顯。

當(dāng)時(shí)發(fā)現(xiàn)這個(gè)問題,是上線后的第二天,幫忙排查線上問題,發(fā)現(xiàn)內(nèi)存較上線前上漲了點(diǎn)(大概幾百兆的樣子),了解glibc內(nèi)存分配原理的都清楚,調(diào)用delete后,內(nèi)存不一定會(huì)歸還給OS,但是本著寧可信其有,不可信其無(wú)的心態(tài),決定來(lái)分析是否真的存在內(nèi)存泄漏。

當(dāng)時(shí)用了個(gè)比較傻瓜式的方法,通過top命令,將該進(jìn)程所占的內(nèi)存輸出到本地文件,大概幾個(gè)小時(shí)后,將這些數(shù)據(jù)導(dǎo)入Excel中,內(nèi)存占用基本呈一條斜線,所以基本能夠確定代碼存在內(nèi)存泄漏,所以就對(duì)新上線的這部分代碼進(jìn)行重新review,定位到泄漏點(diǎn),然后修復(fù),重新上線。

一次性內(nèi)存泄漏

這種內(nèi)存泄漏在程序的生命周期內(nèi)只會(huì)泄漏一次,或者說造成泄漏的代碼只會(huì)被執(zhí)行一次。

有的時(shí)候,這種可能不算內(nèi)存泄漏,或者說設(shè)計(jì)如此。就以筆者現(xiàn)在線上的服務(wù)來(lái)說,類似于如下這種:

intmain(){
auto*service=newService;
//dosth
service->Run();//服務(wù)啟動(dòng)
service->Loop();//可以理解為一個(gè)sleep,目的是使得程序不退出
return0;
}

這種嚴(yán)格意義上,并不算內(nèi)存泄漏,因?yàn)槌绦蚴沁@么設(shè)計(jì)的,即使程序異常退出,那么整個(gè)服務(wù)進(jìn)程也就退出了,當(dāng)然,在Loop()后面加個(gè)delete更好。

隱式內(nèi)存泄漏

程序在運(yùn)行過程中不停的分配內(nèi)存,但是直到結(jié)束的時(shí)候才釋放內(nèi)存。嚴(yán)格的說這里并沒有發(fā)生內(nèi)存泄漏,因?yàn)樽罱K程序釋放了所有申請(qǐng)的內(nèi)存。但是對(duì)于一個(gè)服務(wù)器程序,需要運(yùn)行幾天,幾周甚至幾個(gè)月,不及時(shí)釋放內(nèi)存也可能導(dǎo)致最終耗盡系統(tǒng)的所有內(nèi)存。所以,我們稱這類內(nèi)存泄漏為隱式內(nèi)存泄漏。

比較常見的隱式內(nèi)存泄漏有以下三種:

  • 內(nèi)存碎片:還記得我們之前的那篇文章深入理解glibc內(nèi)存管理精髓,程序跑了幾天之后,進(jìn)程就因?yàn)镺OM導(dǎo)致了退出,就是因?yàn)閮?nèi)存碎片導(dǎo)致剩下的內(nèi)存不能被重新分配導(dǎo)致
  • 即使我們調(diào)用了free/delete,運(yùn)行時(shí)庫(kù)不一定會(huì)將內(nèi)存歸還OS,具體深入理解glibc內(nèi)存管理精髓
  • 用過STL的知道,STL內(nèi)部有一個(gè)自己的allocator,我們可以當(dāng)做一個(gè)memory poll,當(dāng)調(diào)用vector.clear()時(shí)候,內(nèi)存并不會(huì)歸還OS,而是放回allocator,其內(nèi)部根據(jù)一定的策略,在特定的時(shí)候?qū)?nèi)存歸還OS,是不是跟glibc原理很像

分類

未釋放

這種是很常見的,比如下面的代碼:

intfun(){
char*pBuffer=malloc(sizeof(char));

/*Dosomework*/
return0;
}

上面代碼是非常常見的內(nèi)存泄漏場(chǎng)景(也可以使用new來(lái)進(jìn)行分配),我們申請(qǐng)了一塊內(nèi)存,但是在fun函數(shù)結(jié)束時(shí)候沒有調(diào)用free函數(shù)進(jìn)行內(nèi)存釋放。

在C++開發(fā)中,還有一種內(nèi)存泄漏,如下:

classObj{
public:
Obj(intsize){
buffer_=newchar;
}
~Obj(){}
private:
char*buffer_;
};

intfun(){
Objectobj;
//dosth
return0;
}

上面這段代碼中,析構(gòu)函數(shù)沒有釋放成員變量buffer_指向的內(nèi)存,所以在編寫析構(gòu)函數(shù)的時(shí)候,一定要仔細(xì)分析成員變量有沒有申請(qǐng)動(dòng)態(tài)內(nèi)存,如果有,則需要手動(dòng)釋放,我們重新編寫了析構(gòu)函數(shù),如下:

~Object(){
deletebuffer_;
}

在C/C++中,對(duì)于普通函數(shù),如果申請(qǐng)了堆資源,請(qǐng)跟進(jìn)代碼的具體場(chǎng)景調(diào)用free/delete進(jìn)行資源釋放;對(duì)于class,如果申請(qǐng)了堆資源,則需要在對(duì)應(yīng)的析構(gòu)函數(shù)中調(diào)用free/delete進(jìn)行資源釋放。

未匹配

在C++中,我們經(jīng)常使用new操作符來(lái)進(jìn)行內(nèi)存分配,其內(nèi)部主要做了兩件事:

  1. 通過operator new從堆上申請(qǐng)內(nèi)存(glibc下,operator new底層調(diào)用的是malloc)
  2. 調(diào)用構(gòu)造函數(shù)(如果操作對(duì)象是一個(gè)class的話)

對(duì)應(yīng)的,使用delete操作符來(lái)釋放內(nèi)存,其順序正好與new相反:

  1. 調(diào)用對(duì)象的析構(gòu)函數(shù)(如果操作對(duì)象是一個(gè)class的話)
  2. 通過operator delete釋放內(nèi)存
void*operatornew(std::size_tsize){
void*p=malloc(size);
if(p==nullptr){
throw("newfailedtoallocate%zubytes",size);
}
returnp;
}
void*operatornew[](std::size_tsize){
void*p=malloc(size);
if(p==nullptr){
throw("new[]failedtoallocate%zubytes",size);
}
returnp;
}

voidoperatordelete(void*ptr)throw(){
free(ptr);
}
voidoperatordelete[](void*ptr)throw(){
free(ptr);
}

為了加深多這塊的理解,我們舉個(gè)例子:

classTest{
public:
Test(){
std::cout<"inTest"<std::endl;
}
//other
~Test(){
std::cout<"in~Test"<std::endl;
}
};

intmain(){
Test*t=newTest;
//dosth
deletet;
return0;
}

在上述main函數(shù)中,我們使用new 操作符創(chuàng)建一個(gè)Test類指針

  1. 通過operator new申請(qǐng)內(nèi)存(底層malloc實(shí)現(xiàn))
  2. 通過placement new在上述申請(qǐng)的內(nèi)存塊上調(diào)用構(gòu)造函數(shù)
  3. 調(diào)用ptr->~Test()釋放Test對(duì)象的成員變量
  4. 調(diào)用operator delete釋放內(nèi)存

上述過程,可以理解為如下:

//new
void*ptr=malloc(sizeof(Test));
t=new(ptr)Test

//delete
ptr->~Test();
free(ptr);

好了,上述內(nèi)容,我們簡(jiǎn)單的講解了C++中new和delete操作符的基本實(shí)現(xiàn)以及邏輯,那么,我們就簡(jiǎn)單總結(jié)下下產(chǎn)生內(nèi)存泄漏的幾種類型。

new 和 free

仍然以上面的Test對(duì)象為例,代碼如下:

Test*t=newTest;
free(t)

此處會(huì)產(chǎn)生內(nèi)存泄漏,在上面,我們已經(jīng)分析過,new操作符會(huì)先通過operator new分配一塊內(nèi)存,然后在該塊內(nèi)存上調(diào)用placement new即調(diào)用Test的構(gòu)造函數(shù)。而在上述代碼中,只是通過free函數(shù)釋放了內(nèi)存,但是沒有調(diào)用Test的析構(gòu)函數(shù)以釋放Test的成員變量,從而引起內(nèi)存泄漏

new[] 和 delete

intmain(){
Test*t=newTest[10];
//dosth
deletet;
return0;
}

在上述代碼中,我們通過new創(chuàng)建了一個(gè)Test類型的數(shù)組,然后通delete操作符刪除該數(shù)組,編譯并執(zhí)行,輸出如下:

inTest
inTest
inTest
inTest
inTest
inTest
inTest
inTest
inTest
inTest
in~Test

從上面輸出結(jié)果可以看出,調(diào)用了10次構(gòu)造函數(shù),但是只調(diào)用了一次析構(gòu)函數(shù),所以引起了內(nèi)存泄漏。這是因?yàn)檎{(diào)用delete t釋放了通過operator new[]申請(qǐng)的內(nèi)存,即malloc申請(qǐng)的內(nèi)存塊,且只調(diào)用了t[0]對(duì)象的析構(gòu)函數(shù),t[1..9]對(duì)象的析構(gòu)函數(shù)并沒有被調(diào)用。

虛析構(gòu)

記得08年面谷歌的時(shí)候,有一道題,面試官問,std::string能否被繼承,為什么?

當(dāng)時(shí)沒回答上來(lái),后來(lái)過了沒多久,進(jìn)行面試復(fù)盤的時(shí)候,偶然看到繼承需要父類析構(gòu)函數(shù)為virtual,才恍然大悟,原來(lái)考察點(diǎn)在這塊。

下面我們看下std::string的析構(gòu)函數(shù)定義:

~basic_string(){
_M_rep()->_M_dispose(this->get_allocator());
}

這塊需要特別說明下,std::basic_string是一個(gè)模板,而std::string是該模板的一個(gè)特化,即std::basic_string。

typedefstd::basic_string<char>string;

現(xiàn)在我們可以給出這個(gè)問題的答案:不能,因?yàn)閟td::string的析構(gòu)函數(shù)不為virtual,這樣會(huì)引起內(nèi)存泄漏。

仍然以一個(gè)例子來(lái)進(jìn)行證明。

classBase{
public:
Base(){
buffer_=newchar[10];
}

~Base(){
std::cout<"inBase::~Base"<std::endl;
delete[]buffer_;
}
private:
char*buffer_;

};

classDerived:publicBase{
public:
Derived(){}

~Derived(){
std::cout<"intDerived::~Derived"<std::endl;
}
};

intmain(){
Base*base=newDerived;
deletebase;
return0;
}

上面代碼輸出如下:

inBase::~Base

可見,上述代碼并沒有調(diào)用派生類Derived的析構(gòu)函數(shù),如果派生類中在堆上申請(qǐng)了資源,那么就會(huì)產(chǎn)生內(nèi)存泄漏

為了避免因?yàn)槔^承導(dǎo)致的內(nèi)存泄漏,我們需要將父類的析構(gòu)函數(shù)聲明為virtual,代碼如下(只列了部分修改代碼,其他不變):

~Base(){
std::cout<"inBase::~Base"<std::endl;
delete[]buffer_;
}

然后重新執(zhí)行代碼,輸出結(jié)果如下:

intDerived::~Derived
inBase::~Base

借助此文,我們?cè)俅慰偨Y(jié)下存在繼承情況下,構(gòu)造函數(shù)和析構(gòu)函數(shù)的調(diào)用順序。

派生類對(duì)象在創(chuàng)建時(shí)構(gòu)造函數(shù)調(diào)用順序:

  1. 調(diào)用父類的構(gòu)造函數(shù)
  2. 調(diào)用父類成員變量的構(gòu)造函數(shù)
  3. 調(diào)用派生類本身的構(gòu)造函數(shù)

派生類對(duì)象在析構(gòu)時(shí)的析構(gòu)函數(shù)調(diào)用順序:

  1. 執(zhí)行派生類自身的析構(gòu)函數(shù)
  2. 執(zhí)行派生類成員變量的析構(gòu)函數(shù)
  3. 執(zhí)行父類的析構(gòu)函數(shù)

為了避免存在繼承關(guān)系時(shí)候的內(nèi)存泄漏,請(qǐng)遵守一條規(guī)則:無(wú)論派生類有沒有申請(qǐng)堆上的資源,請(qǐng)將父類的析構(gòu)函數(shù)聲明為virtual。

循環(huán)引用

在C++開發(fā)中,為了盡可能的避免內(nèi)存泄漏,自C++11起引入了smart pointer,常見的有shared_ptr、weak_ptr以及unique_ptr等(auto_ptr已經(jīng)被廢棄),其中weak_ptr是為了解決循環(huán)引用而存在,其往往與shared_ptr結(jié)合使用。

下面,我們看一段代碼:

classController{
public:
Controller()=default;

~Controller(){
std::cout<"in~Controller"<std::endl;
}

classSubController{
public:
SubController()=default;

~SubController(){
std::cout<"in~SubController"<std::endl;
}

std::shared_ptrcontroller_;
};

std::shared_ptrsub_controller_;
};

intmain(){
autocontroller=std::make_shared();
autosub_controller=std::make_shared();

controller->sub_controller_=sub_controller;
sub_controller->controller_=controller;
return0;
}

編譯并執(zhí)行上述代碼,發(fā)現(xiàn)并沒有調(diào)用Controller和SubController的析構(gòu)函數(shù),我們嘗試著打印下引用計(jì)數(shù),代碼如下:

intmain(){
autocontroller=std::make_shared();
autosub_controller=std::make_shared();

controller->sub_controller_=sub_controller;
sub_controller->controller_=controller;

std::cout<"controlleruse_count:"<std::endl;
std::cout<"sub_controlleruse_count:"<std::endl;
return0;
}

編譯并執(zhí)行之后,輸出如下:

controlleruse_count:2
sub_controlleruse_count:2

通過上面輸出可以發(fā)現(xiàn),因?yàn)橐糜?jì)數(shù)都是2,所以在main函數(shù)結(jié)束的時(shí)候,不會(huì)調(diào)用controller和sub_controller的析構(gòu)函數(shù),所以就出現(xiàn)了內(nèi)存泄漏。

上面產(chǎn)生內(nèi)存泄漏的原因,就是我們常說的循環(huán)引用。

ecb55b96-e54c-11ec-ba43-dac502259ad0.png

為了解決std::shared_ptr循環(huán)引用導(dǎo)致的內(nèi)存泄漏,我們可以使用std::weak_ptr來(lái)單面去除上圖中的循環(huán)。

classController{
public:
Controller()=default;

~Controller(){
std::cout<"in~Controller"<std::endl;
}

classSubController{
public:
SubController()=default;

~SubController(){
std::cout<"in~SubController"<std::endl;
}

std::weak_ptrcontroller_;
};

std::shared_ptrsub_controller_;
};

在上述代碼中,我們將SubController類中controller_的類型從std::shared_ptr變成std::weak_ptr,重新編譯執(zhí)行,結(jié)果如下:

controlleruse_count:1
sub_controlleruse_count:2
in~Controller
in~SubController

從上面結(jié)果可以看出,controller和sub_controller均以釋放,所以循環(huán)引用引起的內(nèi)存泄漏問題,也得以解決。

ecdb96d0-e54c-11ec-ba43-dac502259ad0.png

可能有人會(huì)問,使用std::shared_ptr可以直接訪問對(duì)應(yīng)的成員函數(shù),如果是std::weak_ptr的話,怎么訪問呢?我們可以使用下面的方式:

std::shared_ptrcontroller=controller_.lock();

即在子類SubController中,如果要使用controller調(diào)用其對(duì)應(yīng)的函數(shù),就可以使用上面的方式。

避免

避免在堆上分配

眾所周知,大部分的內(nèi)存泄漏都是因?yàn)樵诙焉戏峙湟鸬模绻覀儾辉诙焉线M(jìn)行分配,就不會(huì)存在內(nèi)存泄漏了(這不廢話嘛),我們可以根據(jù)具體的使用場(chǎng)景,如果對(duì)象可以在棧上進(jìn)行分配,就在棧上進(jìn)行分配,一方面棧的效率遠(yuǎn)高于堆,另一方面,還能避免內(nèi)存泄漏,我們何樂而不為呢。

手動(dòng)釋放

  • 對(duì)于malloc函數(shù)分配的內(nèi)存,在結(jié)束使用的時(shí)候,使用free函數(shù)進(jìn)行釋放
  • 對(duì)于new操作符創(chuàng)建的對(duì)象,切記使用delete來(lái)進(jìn)行釋放
  • 對(duì)于new []創(chuàng)建的對(duì)象,使用delete[]來(lái)進(jìn)行釋放(使用free或者delete均會(huì)造成內(nèi)存泄漏)

避免使用裸指針

盡可能避免使用裸指針,除非所調(diào)用的lib庫(kù)或者合作部門的接口是裸指針。

intfun(int*ptr){//fun是一個(gè)接口或lib函數(shù)
//dosth

return0;
}

intmain(){}
inta=1000;
int*ptr=&a;
//...
fun(ptr);

return0;
}

在上面的fun函數(shù)中,有一個(gè)參數(shù)ptr,為int *,我們需要根據(jù)上下文來(lái)分析這個(gè)指針是否需要釋放,這是一種很不好的設(shè)計(jì)

使用STL中或者自己實(shí)現(xiàn)對(duì)象

在C++中,提供了相對(duì)完善且可靠的STL供我們使用,所以能用STL的盡可能的避免使用C中的編程方式,比如:

  • 使用std::string 替代char *, string類自己會(huì)進(jìn)行內(nèi)存管理,而且優(yōu)化的相當(dāng)不錯(cuò)
  • 使用std::vector或者std::array來(lái)替代傳統(tǒng)的數(shù)組
  • 其它適合使用場(chǎng)景的對(duì)象

智能指針

自C++11開始,STL中引入了智能指針(smart pointer)來(lái)動(dòng)態(tài)管理資源,針對(duì)使用場(chǎng)景的不同,提供了以下三種智能指針。

unique_ptr

unique_ptr是限制最嚴(yán)格的一種智能指針,用來(lái)替代之前的auto_ptr,獨(dú)享被管理對(duì)象指針?biāo)袡?quán)。當(dāng)unique_ptr對(duì)象被銷毀時(shí),會(huì)在其析構(gòu)函數(shù)內(nèi)刪除關(guān)聯(lián)的原始指針。

unique_ptr對(duì)象分為以下兩類:

  • unique_ptr該類型的對(duì)象關(guān)聯(lián)了單個(gè)Type類型的指針

    std::unique_ptrp1(newType);//c++11
    autop1=std::make_unique();//c++14
    
  • unique_ptr 該類型的對(duì)象關(guān)聯(lián)了多個(gè)Type類型指針,即一個(gè)對(duì)象數(shù)組

    std::unique_ptrp2(newType[n]());//c++11
    autop2=std::make_unique(n);//c++14
    
  • 不可用被復(fù)制

    unique_ptr<int>a(newint(0));
    unique_ptr<int>b=a;//編譯錯(cuò)誤
    unique_ptr<int>b=std::move(a);//可以通過move語(yǔ)義進(jìn)行所有權(quán)轉(zhuǎn)移
    

根據(jù)使用場(chǎng)景,可以使用std::unique_ptr來(lái)避免內(nèi)存泄漏,如下:

voidfun(){
unique_ptr<int>a(newint(0));
//usea
}

在上述fun函數(shù)結(jié)束的時(shí)候,會(huì)自動(dòng)調(diào)用a的析構(gòu)函數(shù),從而釋放其關(guān)聯(lián)的指針。

shared_ptr

與unique_ptr不同的是,unique_ptr是獨(dú)占管理權(quán),而shared_ptr則是共享管理權(quán),即多個(gè)shared_ptr可以共用同一塊關(guān)聯(lián)對(duì)象,其內(nèi)部采用的是引用計(jì)數(shù),在拷貝的時(shí)候,引用計(jì)數(shù)+1,而在某個(gè)對(duì)象退出作用域或者釋放的時(shí)候,引用計(jì)數(shù)-1,當(dāng)引用計(jì)數(shù)為0的時(shí)候,會(huì)自動(dòng)釋放其管理的對(duì)象。

voidfun(){
std::shared_ptra;//a是一個(gè)空對(duì)象
{
std::shared_ptrb=std::make_shared();//分配資源
a=b;//此時(shí)引用計(jì)數(shù)為2
{
std::shared_ptrc=a;//此時(shí)引用計(jì)數(shù)為3
}//c退出作用域,此時(shí)引用計(jì)數(shù)為2
}//b退出作用域,此時(shí)引用計(jì)數(shù)為1
}//a退出作用域,引用計(jì)數(shù)為0,釋放對(duì)象

weak_ptr

weak_ptr的出現(xiàn),主要是為了解決shared_ptr的循環(huán)引用,其主要是與shared_ptr一起來(lái)私用。和shared_ptr不同的地方在于,其并不會(huì)擁有資源,也就是說不能訪問對(duì)象所提供的成員函數(shù),不過,可以通過weak_ptr.lock()來(lái)產(chǎn)生一個(gè)擁有訪問權(quán)限的shared_ptr。

std::weak_ptra;
{
std::shared_ptrb=std::make_shared();
a=b
}//b所對(duì)應(yīng)的資源釋放

RAII

RAIIResource Acquisition is Initialization(資源獲取即初始化)的縮寫,是C++語(yǔ)言的一種管理資源,避免泄漏的用法。

利用的就是C++構(gòu)造的對(duì)象最終會(huì)被銷毀的原則。利用C++對(duì)象生命周期的概念來(lái)控制程序的資源,比如內(nèi)存,文件句柄,網(wǎng)絡(luò)連接等。

RAII的做法是使用一個(gè)對(duì)象,在其構(gòu)造時(shí)獲取對(duì)應(yīng)的資源,在對(duì)象生命周期內(nèi)控制對(duì)資源的訪問,使之始終保持有效,最后在對(duì)象析構(gòu)的時(shí)候,釋放構(gòu)造時(shí)獲取的資源。

簡(jiǎn)單地說,就是把資源的使用限制在對(duì)象的生命周期之中,自動(dòng)釋放。

舉個(gè)簡(jiǎn)單的例子,通常在多線程編程的時(shí)候,都會(huì)用到std::mutex,如下代碼:

std::mutexmutex_;

voidfun(){
mutex_.lock();

if(...){
mutex_.unlock();
return;
}

mutex_.unlock()
}

在上述代碼中,如果if分支多的話,每個(gè)if分支里面都要釋放鎖,如果一不小心忘記釋放,那么就會(huì)造成故障,為了解決這個(gè)問題,我們使用RAII技術(shù),代碼如下:

std::mutexmutex_;

voidfun(){
std::lock_guard<std::mutex>guard(mutex_);

if(...){
return;
}
}

在guard出了fun作用域的時(shí)候,會(huì)自動(dòng)調(diào)用mutex_.lock()進(jìn)行釋放,避免了很多不必要的問題。

定位

在發(fā)現(xiàn)程序存在內(nèi)存泄漏后,往往需要定位泄漏點(diǎn),而定位這一步往往是最困難的,所以經(jīng)常為了定位泄漏點(diǎn),采取各種各樣的方案,甭管方案優(yōu)雅與否,畢竟管他白貓黑貓,抓住老鼠才是好貓,所以在本節(jié),簡(jiǎn)單說下筆者這么多年定位泄漏點(diǎn)的方案,有些比較邪門歪道,您就隨便看看就行。

日志

這種方案的核心思想,就是在每次分配內(nèi)存的時(shí)候,打印指針地址,在釋放內(nèi)存的時(shí)候,打印內(nèi)存地址,這樣在程序結(jié)束的時(shí)候,通過分配和釋放的差,如果分配的條數(shù)大于釋放的條數(shù),那么基本就能確定程序存在內(nèi)存泄漏,然后根據(jù)日志進(jìn)行詳細(xì)分析和定位。

char*fun(){
char*p=(char*)malloc(20);
printf("%s,%d,addressis:%p",__FILE__,__LINE__,p);
//dosth
returnp;
}

intmain(){
fun();

return0;
}

統(tǒng)計(jì)

統(tǒng)計(jì)方案可以理解為日志方案的一種特殊實(shí)現(xiàn),其主要原理是在分配的時(shí)候,統(tǒng)計(jì)分配次數(shù),在釋放的時(shí)候,則是統(tǒng)計(jì)釋放的次數(shù),這樣在程序結(jié)束前判斷這倆值是否一致,就能判斷出是否存在內(nèi)存泄漏。

此方法可幫助跟蹤已分配內(nèi)存的狀態(tài)。為了實(shí)現(xiàn)這個(gè)方案,需要?jiǎng)?chuàng)建三個(gè)自定義函數(shù),一個(gè)用于內(nèi)存分配,第二個(gè)用于內(nèi)存釋放,最后一個(gè)用于檢查內(nèi)存泄漏。代碼如下:

staticunsignedintallocated=0;
staticunsignedintdeallocated=0;
void*Memory_Allocate(size_tsize)
{
void*ptr=NULL;
ptr=malloc(size);
if(NULL!=ptr){
++allocated;
}else{
//Logerror
}
returnptr;
}
voidMemory_Deallocate(void*ptr){
if(pvHandle!=NULL){
free(ptr);
++deallocated;
}
}
intCheck_Memory_Leak(void){
intret=0;
if(allocated!=deallocated){
//Logerror
ret=MEMORY_LEAK;
}else{
ret=OK;
}
returnret;
}

工具

在Linux上比較常用的內(nèi)存泄漏檢測(cè)工具是valgrind,所以咱們就以valgrind為工具,進(jìn)行檢測(cè)。

我們首先看一段代碼:

#include

voidfunc(void){
char*buff=(char*)malloc(10);
}

intmain(void){
func();//產(chǎn)生內(nèi)存泄漏
return0;
}
  • 通過gcc -g leak.c -o leak命令進(jìn)行編譯
  • 執(zhí)行valgrind --leak-check=full ./leak

在上述的命令執(zhí)行后,會(huì)輸出如下:

==9652==Memcheck,amemoryerrordetector
==9652==Copyright(C)2002-2017,andGNUGPL'd,byJulianSewardetal.
==9652==UsingValgrind-3.15.0andLibVEX;rerunwith-hforcopyrightinfo
==9652==Command:./leak
==9652==
==9652==
==9652==HEAPSUMMARY:
==9652==inuseatexit:10bytesin1blocks
==9652==totalheapusage:1allocs,0frees,10bytesallocated
==9652==
==9652==10bytesin1blocksaredefinitelylostinlossrecord1of1
==9652==at0x4C29F73:malloc(vg_replace_malloc.c:309)
==9652==by0x40052E:func(leak.c:4)
==9652==by0x40053D:main(leak.c:8)
==9652==
==9652==LEAKSUMMARY:
==9652==definitelylost:10bytesin1blocks
==9652==indirectlylost:0bytesin0blocks
==9652==possiblylost:0bytesin0blocks
==9652==stillreachable:0bytesin0blocks
==9652==suppressed:0bytesin0blocks
==9652==
==9652==Forlistsofdetectedandsuppressederrors,rerunwith:-s
==9652==ERRORSUMMARY:1errorsfrom1contexts(suppressed:0from0)

valgrind的檢測(cè)信息將內(nèi)存泄漏分為如下幾類:

  • definitely lost:確定產(chǎn)生內(nèi)存泄漏
  • indirectly lost:間接產(chǎn)生內(nèi)存泄漏
  • possibly lost:可能存在內(nèi)存泄漏
  • still reachable:即使在程序結(jié)束時(shí)候,仍然有指針在指向該塊內(nèi)存,常見于全局變量

主要上面輸出的下面幾句:

==9652==by0x40052E:func(leak.c:4)
==9652==by0x40053D:main(leak.c:8)

提示在main函數(shù)(leak.c的第8行)fun函數(shù)(leak.c的第四行)產(chǎn)生了內(nèi)存泄漏,通過分析代碼,原因定位,問題解決。

valgrind不僅可以檢測(cè)內(nèi)存泄漏,還有其他很強(qiáng)大的功能,由于本文以內(nèi)存泄漏為主,所以其他的功能就不在此贅述了,有興趣的可以通過valgrind --help來(lái)進(jìn)行查看

?

對(duì)于Windows下的內(nèi)存泄漏檢測(cè)工具,筆者推薦一款輕量級(jí)功能卻非常強(qiáng)大的工具UMDH,筆者在十二年前,曾經(jīng)在某外企負(fù)責(zé)內(nèi)存泄漏,代碼量幾百萬(wàn)行,光編譯就需要兩個(gè)小時(shí),嘗試了各種工具(免費(fèi)的和收費(fèi)的),最終發(fā)現(xiàn)了UMDH,如果你在Windows上進(jìn)行開發(fā),強(qiáng)烈推薦。

?

經(jīng)驗(yàn)之談

在C/C++開發(fā)過程中,內(nèi)存泄漏是一個(gè)非常常見的問題,其影響相對(duì)來(lái)說遠(yuǎn)低于coredump等,所以遇到內(nèi)存泄漏的時(shí)候,不用過于著急,大不了重啟嘛。

在開發(fā)過程中遵守下面的規(guī)則,基本能90+%避免內(nèi)存泄漏:

  • 良好的編程習(xí)慣,只有有malloc/new,就得有free/delete
  • 盡可能的使用智能指針,智能指針就是為了解決內(nèi)存泄漏而產(chǎn)生
  • 使用log進(jìn)行記錄
  • 也是最重要的一點(diǎn),誰(shuí)申請(qǐng),誰(shuí)釋放

對(duì)于malloc分配內(nèi)存,分配失敗的時(shí)候返回值為NULL,此時(shí)程序可以直接退出了,而對(duì)于new進(jìn)行內(nèi)存分配,其分配失敗的時(shí)候,是拋出std::bad_alloc,所以為了第一時(shí)間發(fā)現(xiàn)問題,不要對(duì)new異常進(jìn)行catch,畢竟內(nèi)存都分配失敗了,程序也沒有運(yùn)行的必要了。

如果我們上線后,發(fā)現(xiàn)程序存在內(nèi)存泄漏,如果不嚴(yán)重的話,可以先暫時(shí)不管線上,同時(shí)進(jìn)行排查定位;如果線上泄漏比較嚴(yán)重,那么第一時(shí)間根據(jù)實(shí)際情況來(lái)決定是否回滾。在定位問題點(diǎn)的時(shí)候,可以采用縮小范圍法,著重分析這次新增的代碼,這樣能夠有效縮短問題解決的時(shí)間。

結(jié)語(yǔ)

C/C++之所以復(fù)雜、效率高,是因?yàn)槠潇`活性,可用直接訪問操作系統(tǒng)API,而正因?yàn)槠潇`活性,就很容易出問題,團(tuán)隊(duì)成員必須愿意按照一定的規(guī)則來(lái)進(jìn)行開發(fā),有完整的review機(jī)制,將問題暴露在上線之前。

這樣才可以把經(jīng)歷放在業(yè)務(wù)本身,而不是查找這些問題上,有時(shí)候往往一個(gè)小問題就能消耗很久的時(shí)間去定位解決,所以,一定要有一個(gè)良好的開發(fā)習(xí)慣。

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 內(nèi)存
    +關(guān)注

    關(guān)注

    8

    文章

    3117

    瀏覽量

    75155
  • C++
    C++
    +關(guān)注

    關(guān)注

    22

    文章

    2119

    瀏覽量

    75047

原文標(biāo)題:內(nèi)存泄漏-原因、避免以及定位

文章出處:【微信號(hào):LinuxHub,微信公眾號(hào):Linux愛好者】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    在OpenVINO? C++代碼中啟用 AddressSanitizer 時(shí)的內(nèi)存泄漏怎么解決?

    在 OpenVINO? C++代碼中啟用 AddressSanitizer 時(shí)遇到內(nèi)存泄漏: \"#0 0xaaaab8558370 in operator new(unsigned
    發(fā)表于 06-23 07:16

    使用OpenVINO?進(jìn)行推理時(shí)的內(nèi)存泄漏怎么解決?

    使用 OpenVINO? 進(jìn)行推理時(shí),內(nèi)存會(huì)隨著時(shí)間的推移而增加,并導(dǎo)致程序崩潰。
    發(fā)表于 03-06 08:29

    內(nèi)存泄漏檢測(cè)工具Sanitizer介紹

    內(nèi)存泄漏,我們經(jīng)常會(huì)遇到,如何檢測(cè)內(nèi)存泄漏,除了我們之前講過的 valgrind,還可以使用 g
    的頭像 發(fā)表于 03-01 14:52 ?685次閱讀

    快問快答: 工廠閥門泄漏故障了怎么檢出?檢測(cè)方法全攻略來(lái)了!

    工廠閥門是工業(yè)生產(chǎn)中不可或缺的關(guān)鍵部件,用于控制流體(氣體或液體)的流動(dòng)。然而,由于長(zhǎng)期使用、操作不當(dāng)或維護(hù)不及時(shí)等原因,閥門經(jīng)常會(huì)出現(xiàn)泄漏故障。閥門泄漏不僅會(huì)
    的頭像 發(fā)表于 02-12 11:13 ?621次閱讀
    快問快答: 工廠閥門<b class='flag-5'>泄漏</b>故障了怎么檢出?檢測(cè)方法全攻略來(lái)了!

    使用DevEco Studio高效解決鴻蒙原生應(yīng)用內(nèi)存問題

    在鴻蒙原生應(yīng)用開發(fā)過程中,可能由于種種原因導(dǎo)致應(yīng)用內(nèi)存未被正常地使用或者歸還至操作系統(tǒng),從而引發(fā)內(nèi)存異常占用、內(nèi)存
    的頭像 發(fā)表于 01-16 14:44 ?687次閱讀

    如何使用DevEco Studio性能調(diào)優(yōu)工具Profiler定位應(yīng)用內(nèi)存問題

    鴻蒙應(yīng)用開發(fā)過程中,可能由于種種原因導(dǎo)致應(yīng)用內(nèi)存未被正的使用或者歸還至操作系統(tǒng),從而引發(fā)內(nèi)存異常占用、內(nèi)存
    的頭像 發(fā)表于 01-16 14:40 ?1732次閱讀
    如何使用DevEco Studio性能調(diào)優(yōu)工具Profiler定位應(yīng)用<b class='flag-5'>內(nèi)存</b>問題

    使用ADS8509時(shí),遇到上電后首次轉(zhuǎn)換結(jié)果不正確的情況,是什么原因導(dǎo)致的?

    在使用ADS8509時(shí),遇到上電后首次轉(zhuǎn)換結(jié)果不正確的情況,但從第二次開始轉(zhuǎn)換結(jié)果就是正確的了,請(qǐng)問下這是什么原因導(dǎo)致的。
    發(fā)表于 12-18 06:14

    虛擬內(nèi)存溢出該怎么處理 虛擬內(nèi)存在服務(wù)器中的應(yīng)用

    、虛擬內(nèi)存溢出的原因 內(nèi)存泄漏 :程序中未正確釋放的內(nèi)存會(huì)導(dǎo)致
    的頭像 發(fā)表于 12-04 09:49 ?777次閱讀

    MOS管泄漏電流的類型和產(chǎn)生原因

    MOS管(金屬氧化物半導(dǎo)體場(chǎng)效應(yīng)晶體管)的泄漏電流是指在MOS管關(guān)斷狀態(tài)下,從源極或漏極到襯底之間仍然存在的微弱電流。這些泄漏電流可能對(duì)電路的性能和穩(wěn)定性產(chǎn)生不利影響,因此需要深入了解其類型和產(chǎn)生原因。
    的頭像 發(fā)表于 10-10 15:11 ?4771次閱讀

    內(nèi)存條接觸不良會(huì)導(dǎo)致哪些情況

    內(nèi)存條接觸不良是一種常見的計(jì)算機(jī)故障,它可能會(huì)導(dǎo)致多種問題,影響計(jì)算機(jī)的正常運(yùn)行。本文將詳細(xì)介紹內(nèi)存條接觸不良可能導(dǎo)致的各種情況,以及如何診斷和解決這些問題。 計(jì)算機(jī)無(wú)法啟動(dòng)
    的頭像 發(fā)表于 09-02 14:36 ?4434次閱讀

    內(nèi)存與主板接觸不良,怎么解決

    內(nèi)存與主板接觸不良是計(jì)算機(jī)常見的故障之一,可能導(dǎo)致計(jì)算機(jī)無(wú)法啟動(dòng)、頻繁死機(jī)、藍(lán)屏等問題。 一、內(nèi)存與主板接觸不良的原因 內(nèi)存條質(zhì)量問題
    的頭像 發(fā)表于 09-02 14:35 ?4055次閱讀

    如何檢測(cè)內(nèi)存泄漏

    檢測(cè)內(nèi)存泄漏是軟件開發(fā)過程中一項(xiàng)至關(guān)重要的任務(wù),它有助于識(shí)別和解決那些導(dǎo)致程序占用過多內(nèi)存資源,從而影響程序性能甚至導(dǎo)致程序崩潰的問題。以下
    的頭像 發(fā)表于 07-30 11:50 ?3365次閱讀

    Air780E/Air780EP/Air780EQ/Air201模塊遇到內(nèi)存死機(jī)如何分析

    平臺(tái)模塊出現(xiàn)死機(jī)問題分析trace32工具下載EPAT抓取底層日志從Ramdump里分析內(nèi)存泄漏問題對(duì)于遇到內(nèi)存不足死機(jī)的問題,可以從ramdump里找出哪些函數(shù)在
    的頭像 發(fā)表于 07-19 16:07 ?904次閱讀
    Air780E/Air780EP/Air780EQ/Air201模塊<b class='flag-5'>遇到</b><b class='flag-5'>內(nèi)存</b>死機(jī)如何分析

    NONOS 1.5.3/1.5.4 SSL內(nèi)存泄漏原因?

    我已經(jīng)通過隨附的代碼驗(yàn)證了當(dāng)發(fā)生 SSL 握手錯(cuò)誤時(shí),會(huì)生成內(nèi)存泄漏 此外,espconn_reconnect_callback不稱為信令ESPCONN_HANDSHAKE - TCP SSL 握手
    發(fā)表于 07-18 07:24

    使用system_show_malloc()檢查內(nèi)存泄漏遇到異常怎么解決?

    我想使用system_show_malloc()檢查內(nèi)存泄漏,但是當(dāng)我調(diào)用該函數(shù)時(shí),我得到了致命的異常: 致命異常 28 (LoadProhibitedCause): epc1
    發(fā)表于 07-10 06:32