日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

【精华】详解Qt中的内存管理机制

發(fā)布時(shí)間:2023/12/10 编程问答 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【精华】详解Qt中的内存管理机制 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

前言

內(nèi)存管理,是對(duì)軟件中內(nèi)存資源的分配與釋放進(jìn)行有效管理的方法和理論。

眾所周知,內(nèi)存管理是軟件開發(fā)的一個(gè)重要的內(nèi)容。軟件規(guī)模越大,內(nèi)存管理可能出現(xiàn)的問(wèn)題越多。如果像C語(yǔ)言一樣手動(dòng)地管理內(nèi)存,一會(huì)給開發(fā)人員帶來(lái)巨大的負(fù)擔(dān),二是手動(dòng)管理內(nèi)存的可靠性較差。

Qt為軟件開發(fā)人員提供了一套內(nèi)存管理機(jī)制,用以替代手動(dòng)內(nèi)存管理。

下面開始逐條講述Qt中的內(nèi)存管理機(jī)制。

一脈相承的棧與堆的內(nèi)存管理

了解C語(yǔ)言的同學(xué)都知道,C語(yǔ)言中的內(nèi)存分配有兩種形式:棧內(nèi)存、堆內(nèi)存。

棧內(nèi)存

棧內(nèi)存的管理是由編譯器來(lái)做的,棧上申請(qǐng)的內(nèi)存變量,生存期由所在作用域決定,超出作用域的棧內(nèi)存變量會(huì)被編譯器自動(dòng)釋放。

值得一提的是,作用域的顯著標(biāo)志是一對(duì)大括號(hào),大括號(hào)內(nèi)部即為作用域內(nèi)部,大括號(hào)外部即為作用域外部。

參考下列代碼:

int main() {int a = 0;return 1; }

變量a在棧內(nèi)存上,main函數(shù)返回時(shí),作用域結(jié)束,a的內(nèi)存自動(dòng)被釋放。

從以上描述也可以看出,棧內(nèi)存的使用是在編譯器嚴(yán)密監(jiān)管之下進(jìn)行的,遵循嚴(yán)格的作用域規(guī)則,所以棧內(nèi)存的大小、申請(qǐng)時(shí)機(jī)、釋放時(shí)機(jī)都能在編譯的時(shí)候確定。

堆內(nèi)存

堆內(nèi)存是另外一種管理方式。堆內(nèi)存最大的特點(diǎn)是可以動(dòng)態(tài)分配,即在運(yùn)行時(shí)可以根據(jù)需要進(jìn)行申請(qǐng)。當(dāng)然隨之而來(lái)的弊端也顯而易見:需要開發(fā)人員對(duì)堆內(nèi)存的釋放進(jìn)行嚴(yán)格管理,稍有疏漏會(huì)導(dǎo)致內(nèi)存泄漏,甚至軟件崩潰等問(wèn)題。

參考下列代碼:

int main() {// 申請(qǐng)堆內(nèi)存int *intArray = (int *)malloc(100);// 使用堆內(nèi)存...// 釋放堆內(nèi)存free(intArray);return 1; }

如上述代碼,堆內(nèi)存分配的寫法區(qū)別于棧內(nèi)存。C語(yǔ)言中,堆內(nèi)存使用malloc分配,使用free釋放。C++中可以使用new分配,使用delete釋放。

至此,我們介紹了C語(yǔ)言中的內(nèi)存管理方式。我們知道Qt是C++的框架,C++是對(duì)C語(yǔ)言的擴(kuò)展,所以C語(yǔ)言中的內(nèi)存管理方式(堆、棧)和動(dòng)態(tài)內(nèi)存管理(堆內(nèi)存釋放問(wèn)題)存在的問(wèn)題,在C++中仍然存在。所以Qt中自然而然也有相同的問(wèn)題。說(shuō)起來(lái)可能有點(diǎn)亂,下面用一張圖來(lái)說(shuō)明它們的關(guān)系:

那么,Qt是如何為我們解決動(dòng)態(tài)內(nèi)存管理問(wèn)題的呢?下面開始正式講解。

使用對(duì)象父子關(guān)系進(jìn)行內(nèi)存管理

使用對(duì)象父子關(guān)系進(jìn)行內(nèi)存管理的原理,簡(jiǎn)述為:

在創(chuàng)建類的對(duì)象時(shí),為對(duì)象指定父對(duì)象指針。當(dāng)父對(duì)象在某一時(shí)刻被銷毀釋放時(shí),父對(duì)象會(huì)先遍歷其所有的子對(duì)象,并逐個(gè)將子對(duì)象銷毀釋放。

為了直觀理解上述過(guò)程,以如下代碼為例進(jìn)行說(shuō)明:

#include <QApplication> #include <QLabel>int main(int argc, char *argv[]) {QApplication a(argc, argv);// 創(chuàng)建主窗口QWidget mainWidget;mainWidget.resize(400, 300);// 創(chuàng)建文字標(biāo)簽QLabel *label = new QLabel("Hello World!", &mainWidget);// 顯示主窗口mainWidget.show();return a.exec(); }

運(yùn)行結(jié)果如下:

上述代碼中,mainWidget為主窗口對(duì)象,類型為QWidgetlabel為子窗口對(duì)象,類型為QLabel *。

注意代碼第13行,在創(chuàng)建label文本標(biāo)簽窗口對(duì)象時(shí),new QLabel的第二個(gè)參數(shù)即為父對(duì)象地址(參考Qt Assistant中QLabel的說(shuō)明文檔),這里給的值是主窗口的地址。

main函數(shù)退出時(shí),mainWidget超出main函數(shù)作用域會(huì)析構(gòu),析構(gòu)時(shí)會(huì)自動(dòng)刪除label窗口對(duì)象,所以這里,我們不需要再寫一行:delete label; 來(lái)釋放label的內(nèi)存,很方便而且又能節(jié)省時(shí)間精力。

使用引用計(jì)數(shù)對(duì)內(nèi)存進(jìn)行管理

引用計(jì)數(shù)

引用計(jì)數(shù)可以說(shuō)是軟件開發(fā)人員必知必會(huì)的知識(shí)點(diǎn),它在內(nèi)存管理領(lǐng)域的地位是數(shù)一數(shù)二的。

引用計(jì)數(shù)的原理,還是力所能及地用最簡(jiǎn)單的話來(lái)描述:

引用計(jì)數(shù)需要從三個(gè)方面來(lái)全面理解:

  • 使用場(chǎng)景:一個(gè)資源,多處使用(使用即引用)。

  • 問(wèn)題:到底誰(shuí)來(lái)釋放資源。

  • 原理:使用一個(gè)整形變量來(lái)統(tǒng)計(jì),此資源在多少個(gè)地方被使用,此變量稱為引用計(jì)數(shù)。當(dāng)某處使用完資源以后,將引用計(jì)數(shù)減1。當(dāng)引用計(jì)數(shù)為0時(shí),即沒有任何地方再使用此資源時(shí),真正釋放此資源。這里的資源,在動(dòng)態(tài)內(nèi)存管理中就是指堆內(nèi)存。

  • 用一句話描述就是:誰(shuí)最后使用資源,誰(shuí)負(fù)責(zé)釋放資源

    我們很容易聯(lián)想到現(xiàn)實(shí)中的例子,就是日常生活中的刷碗問(wèn)題的解決方案,即誰(shuí)最后吃完誰(shuí)刷碗。

    需要說(shuō)明的是,引用計(jì)數(shù)不僅僅是在內(nèi)存管理中使用,它是一個(gè)通用的機(jī)制,凡是涉及到資源管理的問(wèn)題,都可以考慮使用引用計(jì)數(shù)。

    下面將要介紹基于引用計(jì)數(shù)原理的兩種衍生的機(jī)制:顯式共享和隱式共享。

    顯式共享

    顯式共享,是僅僅使用引用計(jì)數(shù)控制資源的生命周期的一種共享管理機(jī)制。這種機(jī)制下,無(wú)論資源在何處被引用,自始至終所有引用指向資源都是同一個(gè)。

    之所以叫顯式共享,是因?yàn)檫@種共享方式很直接,沒有隱含的操作,如:Copy on Write寫時(shí)拷貝(見隱式共享的相關(guān)說(shuō)明)。如果想要拷貝并建立新的引用計(jì)數(shù),必須手動(dòng)調(diào)用detach()函數(shù)。

    從使用者的角度看,從頭到尾資源只有一份,一個(gè)地方修改了,另一個(gè)地方就能讀取到修改后的資源。

    **相關(guān)Qt類:**QExplicitlySharedDataPointer,更加深入的用法和編碼,需要參考Qt文檔中的相關(guān)說(shuō)明及Demo。

    隱式共享

    隱式共享,也是一種基于引用計(jì)數(shù)的控制資源的生命周期的共享管理機(jī)制。

    隱式共享,對(duì)不同的操作有不同的處理:

    • 讀取時(shí),在所有引用的地方使用同一個(gè)資源;

    • 在寫入、修改時(shí)自動(dòng)復(fù)制一份資源出來(lái)做修改,自動(dòng)脫離原始的引用計(jì)數(shù),因?yàn)槭切碌馁Y源,所以要建立新的引用計(jì)數(shù)。這種操作叫Copy on Write寫時(shí)復(fù)制技術(shù),是自動(dòng)隱含進(jìn)行的。

    從使用者的角度看,每個(gè)使用者都像是擁有獨(dú)立的一份資源。在一個(gè)地方修改,修改的只是原始資源的拷貝,不會(huì)影響原始資源的內(nèi)容,自然就不會(huì)影響到其他使用者。所以這種共享方式稱為隱式共享。

    相關(guān)Qt類有QString、QByteArray、QImage、QList、QMap、QHash等。

    推薦閱讀:Qt文檔中的Implicit Sharing專題。

    智能指針

    智能指針是對(duì)C/C++指針的擴(kuò)展,同樣基于引用計(jì)數(shù)。

    智能指針和顯示共享和隱式共享有何區(qū)別?它們區(qū)別是:智能指針是輕量級(jí)的引用計(jì)數(shù),它將顯式共享、隱式共享中的引用計(jì)數(shù)實(shí)現(xiàn)部分單獨(dú)提取了出來(lái),制作成模板類,形成了多種特性各異的指針。

    例如,QString除了實(shí)現(xiàn)引用計(jì)數(shù),還實(shí)現(xiàn)了字符串相關(guān)的豐富的操作接口。QList也實(shí)現(xiàn)了引用計(jì)數(shù),還實(shí)現(xiàn)了列表這種數(shù)據(jù)結(jié)構(gòu)的各種操作。可以說(shuō),顯式共享和隱式共享一般是封裝在功能類中的,不需要開發(fā)者來(lái)管理。

    智能指針將引用計(jì)數(shù)功能剝離出來(lái),為Qt開發(fā)者提供了便捷的引用計(jì)數(shù)基礎(chǔ)設(shè)施。

    強(qiáng)(智能)指針

    Qt中的強(qiáng)指針實(shí)現(xiàn)類是:QSharedPointer,此類是模板類,可以指向多種類型的數(shù)據(jù),主要用來(lái)管理堆內(nèi)存。關(guān)于QSharedPointer在Qt Assistant中有詳細(xì)描述。

    它的原理和顯式共享一樣:最后使用的地方負(fù)責(zé)釋放刪除資源,如類對(duì)象、內(nèi)存塊。

    強(qiáng)指針中的“強(qiáng)”,是指每多一個(gè)使用者,引用計(jì)數(shù)都會(huì)老老實(shí)實(shí)地**+1**。而弱指針就不同,下面就接著講解弱指針。

    弱(智能)指針

    Qt中的弱指針實(shí)現(xiàn)類是QWeakPointer,此類亦為模板類,可以指向多種類型的數(shù)據(jù),同樣主要用來(lái)管理堆內(nèi)存。關(guān)于QWeakPointer在Qt Assistant中有詳細(xì)描述。

    弱指針只能從強(qiáng)指針QSharedPointer轉(zhuǎn)化而來(lái),獲取弱指針,不增加引用計(jì)數(shù),它只是一個(gè)強(qiáng)指針的觀察者,觀察而不干預(yù)。只要強(qiáng)指針存在,弱指針也可以轉(zhuǎn)換成強(qiáng)指針。可見弱指針和強(qiáng)指針是一對(duì)形影不離的組合,通常結(jié)合起來(lái)使用。

    局部指針

    局部指針,是一種超出作用域自動(dòng)刪除、釋放堆內(nèi)存、對(duì)象的工具。它結(jié)合了棧內(nèi)存管理和堆內(nèi)存管理的優(yōu)點(diǎn)。

    Qt中的實(shí)現(xiàn)類有:QScopedPointer,QScopedArrayPointer,具體可以參考Qt Assistant。

    觀察者指針

    上面說(shuō)弱指針的時(shí)候,講到過(guò)觀察者。觀察者是指僅僅做查詢作用的指針,不會(huì)影響到引用計(jì)數(shù)。

    Qt中的觀察者指針是QPointer,它必須指向QObject的子類對(duì)象,才能對(duì)對(duì)象生命周期進(jìn)行觀察。因?yàn)橹挥蠶Object子類才會(huì)在析構(gòu)的時(shí)候通知QPointer已失效。

    QPointer是防止懸掛指針(即野指針)的有效手段,因?yàn)樗笇?duì)象一旦被刪除,QPointer會(huì)自動(dòng)置空,在使用時(shí),判斷指針是否為空即可,不為空說(shuō)明對(duì)象可以使用,不會(huì)產(chǎn)生內(nèi)存訪問(wèn)錯(cuò)誤的問(wèn)題。

    總結(jié)

    本篇文章講解了Qt中的各種內(nèi)存管理機(jī)制,算是做了一個(gè)比較全面的描述。

    之所以說(shuō)是必讀,是因?yàn)楣P者在工作中發(fā)現(xiàn),內(nèi)存管理確實(shí)非常重要。Qt內(nèi)存管理機(jī)制是貫穿整個(gè)Qt中所有類的核心線索之一,搞懂了內(nèi)存管理

    • 能在腦海中形成內(nèi)存中對(duì)象的布局圖,寫代碼的時(shí)候才能下筆如有神,管理起項(xiàng)目中眾多的對(duì)象才能游刃有余,提高開發(fā)效率;
    • 能夠減少bug的產(chǎn)生。有經(jīng)驗(yàn)的開發(fā)者應(yīng)該知道,內(nèi)存問(wèn)題很難調(diào)試定位到具體的位置,往往導(dǎo)致奇怪的bug出現(xiàn)。
    • 能夠幫助理解Qt眾多類的底層不變的邏輯,學(xué)起來(lái)更容易。

    本文只是對(duì)Qt中內(nèi)存管理進(jìn)行了梳理,無(wú)法涵蓋很多細(xì)節(jié)問(wèn)題,讀者需要花一些時(shí)間去詳細(xì)閱讀Qt助手文檔,最好是寫幾個(gè)demo測(cè)試驗(yàn)證。花時(shí)間是值得的,因?yàn)榧夹g(shù)是日新月異的,但是核心的原理變化是不大的。Qt中的內(nèi)存管理思想和方法,在很多語(yǔ)言、框架中(Python、Objective C、JavaScript等等)都有類似的應(yīng)用。

    值得一提的是,之所以Qt中具有各種各樣的內(nèi)存管理方式,是因?yàn)樗軌驕p輕開發(fā)者的負(fù)擔(dān),更加專注于業(yè)務(wù)代碼的實(shí)現(xiàn),而不是被內(nèi)存問(wèn)題折騰的焦頭爛額。不使用Qt中的內(nèi)存管理,只用C的手動(dòng)內(nèi)存管理仍然可以寫可以運(yùn)行的代碼!前提是不考慮成本問(wèn)題,并假設(shè)開發(fā)者在內(nèi)存問(wèn)題上不會(huì)犯錯(cuò)。總之一句話,不要對(duì)立各種技術(shù),每種技術(shù)都有適用的場(chǎng)景,拋開場(chǎng)景談方法都是不理智的。

    后面會(huì)根據(jù)需要專門講解一些細(xì)節(jié)問(wèn)題,敬請(qǐng)關(guān)注!


    本文首發(fā)自公眾號(hào)“Qt未來(lái)工程師”,歡迎關(guān)注。

    總結(jié)

    以上是生活随笔為你收集整理的【精华】详解Qt中的内存管理机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。