国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入理解C++內(nèi)存管理

這篇具有很好參考價值的文章主要介紹了深入理解C++內(nèi)存管理。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報違法"按鈕提交疑問。

引言

C++的高抽象層次,又兼具高性能,是其他語言所無法替代的,C++標(biāo)準(zhǔn)保持穩(wěn)定發(fā)展,更加現(xiàn)代化,更加強(qiáng)大。但在各種活躍編程語言中,C++門檻依然很高,尤其C++的內(nèi)存問題(內(nèi)存泄露,內(nèi)存溢出,內(nèi)存宕機(jī),堆棧破壞等問題),需要理解C++標(biāo)準(zhǔn)對象模型,C++標(biāo)準(zhǔn)庫,標(biāo)準(zhǔn)C庫,操作系統(tǒng)等內(nèi)存設(shè)計(jì),才能更加深入理解C++內(nèi)存管理。

一、C++的內(nèi)存布局

深入理解C++內(nèi)存管理

如圖,描述了C++程序的內(nèi)存分布。

Code Segment(代碼區(qū)):也稱Text Segment,存放可執(zhí)行程序的機(jī)器碼。

Data Segment (數(shù)據(jù)區(qū)):存放已初始化的全局和靜態(tài)變量, 常量數(shù)據(jù)(如字符串常量)。

BSS(Block started by symbol):存放未初始化的全局和靜態(tài)變量。(默認(rèn)設(shè)為0)

Heap(堆):從低地址向高地址增長。容量大于棧,程序中動態(tài)分配的內(nèi)存在此區(qū)域。

Stack(棧):從高地址向低地址增長。由編譯器自動管理分配。程序中的局部變量、函數(shù)參數(shù)值、返回變量等存在此區(qū)域。

?1.1 函數(shù)棧

如上圖所示,可執(zhí)行程序的文件包含BSS,Data Segment和Code Segment,當(dāng)可執(zhí)行程序載入內(nèi)存后,系統(tǒng)會保留一些空間,即堆區(qū)和棧區(qū)。堆區(qū)主要是動態(tài)分配的內(nèi)存(默認(rèn)情況下),而棧區(qū)主要是函數(shù)以及局部變量等(包括main函數(shù))。一般而言,棧的空間遠(yuǎn)遠(yuǎn)堆的空間。

當(dāng)調(diào)用函數(shù)時,一塊連續(xù)內(nèi)存(堆棧幀)壓入棧;函數(shù)返回時,堆棧幀彈出。

堆棧幀包含如下數(shù)據(jù):

① 函數(shù)返回地址

② 局部變量/CPU寄存器數(shù)據(jù)備份

深入理解C++內(nèi)存管理

1.2 全局變量

當(dāng)全局/靜態(tài)變量(如下代碼中的x和y變量)未初始化的時候,它們記錄在BSS段。

int x;
int z = 5;
void func()
{
     static int y;
}
int main()
{
    return 0;
}

處于BSS段的變量的值默認(rèn)為0,考慮到這一點(diǎn),BSS段內(nèi)部無需存儲大量的零值,而只需記錄字節(jié)個數(shù)即可。

系統(tǒng)載入可執(zhí)行程序后,將BSS段的數(shù)據(jù)載入數(shù)據(jù)段(Data Segment) ,并將內(nèi)存初始化為0,再調(diào)用程序入口(main函數(shù))。而對于已經(jīng)初始化了的全局/靜態(tài)變量而言,如以上代碼中的z變量,則一直存儲于數(shù)據(jù)段(Data Segment)。

1.2 內(nèi)存對齊

對于基礎(chǔ)類型,如float, double, int, char等,它們的大小和內(nèi)存占用是一致的。而對于結(jié)構(gòu)體而言,如果我們?nèi)〉闷鋝izeof的結(jié)果,會發(fā)現(xiàn)這個值有可能會大于結(jié)構(gòu)體內(nèi)所有成員大小的總和,這是由于結(jié)構(gòu)體內(nèi)部成員進(jìn)行了內(nèi)存對齊

1.2.1 為什么要內(nèi)存對齊

① 內(nèi)存對齊使數(shù)據(jù)讀取更高效

在硬件設(shè)計(jì)上,數(shù)據(jù)讀取的處理器只能從地址為k的倍數(shù)的內(nèi)存處開始讀取數(shù)據(jù)。這種讀取方式相當(dāng)于將內(nèi)存分為了多個"塊“,假設(shè)內(nèi)存可以從任意位置開始存放的話,數(shù)據(jù)很可能會被分散到多個“塊”中,處理分散在多個塊中的數(shù)據(jù)需要移除首尾不需要的字節(jié),再進(jìn)行合并,非常耗時。

為了提高數(shù)據(jù)讀取的效率,程序分配的內(nèi)存并不是連續(xù)存儲的,而是按首地址為k的倍數(shù)的方式存儲;這樣就可以一次性讀取數(shù)據(jù),而不需要額外的操作。

深入理解C++內(nèi)存管理

② 在某些平臺下,不進(jìn)行內(nèi)存對齊會崩潰?

1.2.2如何不使用內(nèi)存對齊

當(dāng)然有一些地方為了減少內(nèi)存的開銷就會可以避免內(nèi)存對齊,比如SDS使用專門的編譯優(yōu)化來節(jié)省內(nèi)存空間?:

struct __attribute__ ((__packed__)) sdshdr8

attribute ((packed))的作用就是告訴編譯器,在編譯 sdshdr8 結(jié)構(gòu)時,不要使用字節(jié)對齊的方式,而是采用緊湊的方式分配內(nèi)存。這是因?yàn)樵谀J(rèn)情況下,編譯器會按照 8 字節(jié)對齊的方式,給變量分配內(nèi)存。也就是說,即使一個變量的大小不到 8 個字節(jié),編譯器也會給它分配 8 個字節(jié)。

為了節(jié)省內(nèi)存,Redis 在這方面的設(shè)計(jì)上可以說是精打細(xì)算的。所以,Redis 采用了attribute ((packed))屬性定義結(jié)構(gòu)體,這樣一來,結(jié)構(gòu)體實(shí)際占用多少內(nèi)存空間,編譯器就分配多少空間。

比如,我用attribute ((packed))屬性定義結(jié)構(gòu)體 s2,同樣包含 char 和 int 兩個類型的成員變量,代碼如下所示:

  #include <stdio.h>
  int main() {
     struct __attribute__((packed)) s2{
        char a;
        int b;
     } ts2;
     printf("%lu\n", sizeof(ts2));
     return 0;
  }

當(dāng)你運(yùn)行這段代碼時,你可以看到,打印的結(jié)果是 5,表示編譯器用了緊湊型內(nèi)存分配,s2 結(jié)構(gòu)體只占用 5 個字節(jié)的空間。

總而言之,如果你在開發(fā)程序時,希望能節(jié)省數(shù)據(jù)結(jié)構(gòu)的內(nèi)存開銷,就可以把a(bǔ)ttribute ((packed))這個編程方法用起來。

1.2.3?內(nèi)存對齊的規(guī)則

定義有效對齊值(alignment)為結(jié)構(gòu)體中 最寬成員 和 編譯器/用戶指定對齊值 中較小的那個。

(1) 結(jié)構(gòu)體起始地址為有效對齊值的整數(shù)倍

(2) 結(jié)構(gòu)體總大小為有效對齊值的整數(shù)倍

(3) 結(jié)構(gòu)體第一個成員偏移值為0,之后成員的偏移值為 min(有效對齊值, 自身大小) 的整數(shù)倍

相當(dāng)于每個成員要進(jìn)行對齊,并且整個結(jié)構(gòu)體也需要進(jìn)行對齊。

struct A
{
    int i;
    char c1;
    char c2;
};

int main()
{
    cout << sizeof(A) << endl; // 有效對齊值為4, output : 8
    return 0;
}

1.3 內(nèi)存碎片

程序的內(nèi)存往往不是緊湊連續(xù)排布的,而是存在著許多碎片。我們根據(jù)碎片產(chǎn)生的原因把碎片分為內(nèi)部碎片和外部碎片兩種類型:

(1) 內(nèi)部碎片:系統(tǒng)分配的內(nèi)存大于實(shí)際所需的內(nèi)存(由于對齊機(jī)制);

(2) 外部碎片:不斷分配回收不同大小的內(nèi)存,由于內(nèi)存分布散亂,較大內(nèi)存無法分配;

深入理解C++內(nèi)存管理

1.4 繼承類布局?

繼承

如果一個類繼承自另一個類,那么它自身的數(shù)據(jù)位于父類之后。

含虛函數(shù)的類

如果當(dāng)前類包含虛函數(shù),則會在類的最前端占用4個字節(jié),用于存儲虛表指針(vpointer),它指向一個虛函數(shù)表(vtable)。vtable中包含當(dāng)前類的所有虛函數(shù)指針。

二、C++內(nèi)存泄漏

2.1 什么是內(nèi)存

一般我們常說的內(nèi)存泄漏是指堆內(nèi)存的泄漏。堆內(nèi)存是指程序從堆中分配的,大小任意的(內(nèi)存塊的大小可以在程序運(yùn)行期決定),使用完后必須顯示釋放的內(nèi)存。應(yīng)用程序一般使用malloc,realloc,new等函數(shù)從堆中分配到一塊內(nèi)存,使用完后,程序必須負(fù)責(zé)相應(yīng)的調(diào)用free或delete釋放該內(nèi)存塊,否則,這塊內(nèi)存就不能被再次使用,我們就說這塊內(nèi)存泄漏了。以下這段小程序演示了堆內(nèi)存發(fā)生泄漏的情形:

void MyFunction(int nSize)

{

 char* p= new char[nSize];

 if( !GetStringFrom( p, nSize ) ){

  MessageBox(“Error”);

  return;

 }

 …//using the string pointed by p;

 delete p;

}

當(dāng)函數(shù)GetStringFrom()返回零的時候,指針p指向的內(nèi)存就不會被釋放。這是一種常見的發(fā)生內(nèi)存泄漏的情形。程序在入口處分配內(nèi)存,在出口處釋放內(nèi)存,但是c函數(shù)可以在任何地方退出,所以一旦有某個出口處沒有釋放應(yīng)該釋放的內(nèi)存,就會發(fā)生內(nèi)存泄漏。

廣義的說,內(nèi)存泄漏不僅僅包含堆內(nèi)存的泄漏,還包含系統(tǒng)資源的泄漏(resource leak),比如核心態(tài)HANDLE,GDI Object,SOCKET,?Interface等,從根本上說這些由操作系統(tǒng)分配的對象也消耗內(nèi)存,如果這些對象發(fā)生泄漏最終也會導(dǎo)致內(nèi)存的泄漏。而且,某些對象消耗的是核心態(tài)內(nèi)存,這些對象嚴(yán)重泄漏時會導(dǎo)致整個操作系統(tǒng)不穩(wěn)定。

2.2 內(nèi)存泄漏的發(fā)生方式

以發(fā)生的方式來分類,內(nèi)存泄漏可以分為4類:

  1.?常發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼會被多次執(zhí)行到,每次被執(zhí)行的時候都會導(dǎo)致一塊內(nèi)存泄漏。比如例二,如果Something()函數(shù)一直返回True,那么pOldBmp指向的HBITMAP對象總是發(fā)生泄漏。

  2.?偶發(fā)性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只有在某些特定環(huán)境或操作過程下才會發(fā)生。比如例二,如果Something()函數(shù)只有在特定環(huán)境下才返回True,那么pOldBmp指向的HBITMAP對象并不總是發(fā)生泄漏。常發(fā)性和偶發(fā)性是相對的。對于特定的環(huán)境,偶發(fā)性的也許就變成了常發(fā)性的。所以測試環(huán)境和測試方法對檢測內(nèi)存泄漏至關(guān)重要。

? ? ? ?3.?一次性內(nèi)存泄漏。發(fā)生內(nèi)存泄漏的代碼只會被執(zhí)行一次,或者由于算法上的缺陷,導(dǎo)致總會有一塊僅且一塊內(nèi)存發(fā)生泄漏。比如,在類的構(gòu)造函數(shù)中分配內(nèi)存,在析構(gòu)函數(shù)中卻沒有釋放該內(nèi)存,但是因?yàn)檫@個類是一個Singleton,所以內(nèi)存泄漏只會發(fā)生一次。另一個例子:

char* g_lpszFileName = NULL;

void SetFileName( const char* lpcszFileName )
{

 if( g_lpszFileName ){
  free( g_lpszFileName );
 }

 g_lpszFileName = strdup( lpcszFileName );
}

如果程序在結(jié)束的時候沒有釋放g_lpszFileName指向的字符串,那么,即使多次調(diào)用SetFileName(),總會有一塊內(nèi)存,而且僅有一塊內(nèi)存發(fā)生泄漏。

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

class Connection

{

 public:

  Connection( SOCKET s);
  ~Connection();
  …

 private:

  SOCKET _socket;
  …

};

class ConnectionManager

{

 public:

  ConnectionManager(){}

  ~ConnectionManager(){

   list::iterator it;
   for( it = _connlist.begin(); it != _connlist.end(); ++it ){
    delete (*it);
   }
   _connlist.clear();

  }

  void OnClientConnected( SOCKET s ){
   Connection* p = new Connection(s);
   _connlist.push_back(p);
  }

  void OnClientDisconnected( Connection* pconn ){
   _connlist.remove( pconn );
   delete pconn;
  }

 private:

  list _connlist;

};

假設(shè)在Client從Server端斷開后,Server并沒有呼叫OnClientDisconnected()函數(shù),那么代表那次連接的Connection對象就不會被及時的刪除(在Server程序退出的時候,所有Connection對象會在ConnectionManager的析構(gòu)函數(shù)里被刪除)。當(dāng)不斷的有連接建立、斷開時隱式內(nèi)存泄漏就發(fā)生了。

從用戶使用程序的角度來看,內(nèi)存泄漏本身不會產(chǎn)生什么危害,作為一般的用戶,根本感覺不到內(nèi)存泄漏的存在。真正有危害的是內(nèi)存泄漏的堆積,這會最終消耗盡系統(tǒng)所有的內(nèi)存。從這個角度來說,一次性內(nèi)存泄漏并沒有什么危害,因?yàn)樗粫逊e,而隱式內(nèi)存泄漏危害性則非常大,因?yàn)檩^之于常發(fā)性和偶發(fā)性內(nèi)存泄漏它更難被檢測到。

2.3?檢測內(nèi)存泄漏

檢測內(nèi)存泄漏的關(guān)鍵是要能截獲住對分配內(nèi)存和釋放內(nèi)存的函數(shù)的調(diào)用。截獲住這兩個函數(shù),我們就能跟蹤每一塊內(nèi)存的生命周期,比如,每當(dāng)成功的分配一塊內(nèi)存后,就把它的指針加入一個全局的list中;每當(dāng)釋放一塊內(nèi)存,再把它的指針從list中刪除。這樣,當(dāng)程序結(jié)束的時候,list中剩余的指針就是指向那些沒有被釋放的內(nèi)存。

如果要檢測堆內(nèi)存的泄漏,那么需要截獲住malloc/realloc/free和new/delete就可以了(其實(shí)new/delete最終也是用malloc/free的,所以只要截獲前面一組即可)。對于其他的泄漏,可以采用類似的方法,截獲住相應(yīng)的分配和釋放函數(shù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-404762.html

到了這里,關(guān)于深入理解C++內(nèi)存管理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 深入理解Linux內(nèi)核——內(nèi)存管理(1)

    深入理解Linux內(nèi)核——內(nèi)存管理(1)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(26)
  • 深入理解C&C++內(nèi)存管理

    深入理解C&C++內(nèi)存管理

    我們先來看一下下面的代碼,分析一下對于每個變量的存儲位置: 【說明】 棧 又叫堆棧–非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長的。 內(nèi)存映射段 是高效的I/O映射方式,用于裝載一個共享的動態(tài)內(nèi)存庫。用戶可使用系統(tǒng)接口創(chuàng)建共享共享內(nèi)存,做進(jìn)程間通信。

    2023年04月26日
    瀏覽(26)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(3)

    深入理解Linux內(nèi)核——內(nèi)存管理(3)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月13日
    瀏覽(26)
  • 深入理解Linux虛擬內(nèi)存管理(六)

    深入理解Linux虛擬內(nèi)存管理(六)

    Linux 內(nèi)核設(shè)計(jì)與實(shí)現(xiàn) 深入理解 Linux 內(nèi)核 Linux 設(shè)備驅(qū)動程序 Linux設(shè)備驅(qū)動開發(fā)詳解 深入理解Linux虛擬內(nèi)存管理(一) 深入理解Linux虛擬內(nèi)存管理(二) 深入理解Linux虛擬內(nèi)存管理(三) 深入理解Linux虛擬內(nèi)存管理(四) 深入理解Linux虛擬內(nèi)存管理(五) 深入理解Linux虛擬內(nèi)存

    2024年02月08日
    瀏覽(23)
  • 深入理解 Spark(四)Spark 內(nèi)存管理模型

    深入理解 Spark(四)Spark 內(nèi)存管理模型

    Executor 進(jìn)程作為一個 JVM 進(jìn)程,其內(nèi)存管理建立在 JVM 的內(nèi)存管理之上,整個大致包含兩種方式:堆內(nèi)內(nèi)存和堆外內(nèi)存。 一個 Executor 當(dāng)中的所有 Task 是共享堆內(nèi)內(nèi)存的。一個 Work 中的多個 Executor 中的多個 Task 是共享堆外內(nèi)存的。 堆內(nèi)內(nèi)存和堆外內(nèi)存 大數(shù)據(jù)領(lǐng)域兩個比較常見

    2024年01月24日
    瀏覽(23)
  • 深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    深入理解Linux內(nèi)核——內(nèi)存管理(4)——伙伴系統(tǒng)(1)

    提要:本系列文章主要參考 MIT 6.828課程 以及兩本書籍 《深入理解Linux內(nèi)核》 《深入Linux內(nèi)核架構(gòu)》 對Linux內(nèi)核內(nèi)容進(jìn)行總結(jié)。 內(nèi)存管理的實(shí)現(xiàn)覆蓋了多個領(lǐng)域: 內(nèi)存中的物理內(nèi)存頁的管理 分配大塊內(nèi)存的伙伴系統(tǒng) 分配較小內(nèi)存的slab、slub、slob分配器 分配非連續(xù)內(nèi)存塊的

    2024年02月10日
    瀏覽(22)
  • Python內(nèi)存管理與垃圾回收機(jī)制:深入理解與優(yōu)化【第138篇—RESTful API】

    Python內(nèi)存管理與垃圾回收機(jī)制:深入理解與優(yōu)化【第138篇—RESTful API】

    前些天發(fā)現(xiàn)了一個巨牛的人工智能學(xué)習(xí)網(wǎng)站,通俗易懂,風(fēng)趣幽默,忍不住分享一下給大家。【點(diǎn)擊進(jìn)入巨牛的人工智能學(xué)習(xí)網(wǎng)站】。 在Python編程中,內(nèi)存管理與垃圾回收機(jī)制是至關(guān)重要的主題。了解Python如何管理內(nèi)存和處理垃圾回收對于編寫高效、穩(wěn)定的程序至關(guān)重要。本

    2024年03月18日
    瀏覽(21)
  • 【c/c++】深入探秘:C++內(nèi)存管理的機(jī)制

    【c/c++】深入探秘:C++內(nèi)存管理的機(jī)制

    ??個人主頁 : Quitecoder ?? 專欄 : c++筆記倉 朋友們大家好,本篇文章我們詳細(xì)講解c++中的動態(tài)內(nèi)存管理 我們來看內(nèi)存區(qū)域劃分 數(shù)據(jù)段就是我們所說的 全局變量 ,代碼段是我們所說的 常量區(qū) ,我們需要重點(diǎn)關(guān)注的是 堆區(qū) ,這部分是由我們自己控制的 我們來依次討論:

    2024年04月16日
    瀏覽(21)
  • 深入理解計(jì)算機(jī)系統(tǒng)(13)_存儲器層次結(jié)構(gòu)

    深入理解計(jì)算機(jī)系統(tǒng)(13)_存儲器層次結(jié)構(gòu)

    第一章 計(jì)算機(jī)的基本組成 1. 內(nèi)容概述 2. 計(jì)算機(jī)基本組成 第二章 計(jì)算機(jī)的指令和運(yùn)算 3. 計(jì)算機(jī)指令 4. 程序的機(jī)器級表示 5. 計(jì)算機(jī)運(yùn)算 6. 信息表示與處理 第三章 處理器設(shè)計(jì) 7. CPU 8. 處理器體系結(jié)構(gòu) 9. 優(yōu)化程序性能 10. 其他處理器 第四章 存儲器和IO系統(tǒng) 11. 存儲器的層次結(jié)構(gòu)

    2024年02月16日
    瀏覽(23)
  • 【C++干貨基地】深度理解C++中的高效內(nèi)存管理方式 new & delete

    【C++干貨基地】深度理解C++中的高效內(nèi)存管理方式 new & delete

    ?? 鴿芷咕 :個人主頁 ??? 個人專欄 : 《C++干貨基地》《粉絲福利》 ??生活的理想,就是為了理想的生活! ??哈嘍各位鐵汁們好啊,我是博主鴿芷咕《C++干貨基地》是由我的襄陽家鄉(xiāng)零食基地有感而發(fā),不知道各位的城市有沒有這種實(shí)惠又全面的零食基地呢?C++ 本身作

    2024年04月26日
    瀏覽(36)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包