<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C語言內(nèi)存精講,圖文分析內(nèi)存四區(qū)

          共 3958字,需瀏覽 8分鐘

           ·

          2021-11-30 11:47


          鏈接:https://www.cnblogs.com/yif1991/p/5049638.html

          在計算機(jī)系統(tǒng),特別是嵌入式系統(tǒng)中,內(nèi)存資源是非常有限的。尤其對于移動端開發(fā)者來說,硬件資源的限制使得其在程序設(shè)計中首要考慮的問題就是如何有效地管理內(nèi)存資源。本文是作者在學(xué)習(xí)C語言內(nèi)存管理的過程中做的一個總結(jié),如有不妥之處,望讀者不吝指正。

          一、幾個基本概念

          在C語言中,關(guān)于內(nèi)存管理的知識點比較多,如函數(shù)、變量、作用域、指針等,在探究C語言內(nèi)存管理機(jī)制時,先簡單復(fù)習(xí)下這幾個基本概念:

          • 1.變量:不解釋。但需要搞清楚這幾種變量類型:

          全局變量(外部變量):出現(xiàn)在代碼塊{}之外的變量就是全局變量。局部變量(自動變量):一般情況下,代碼塊{}內(nèi)部定義的變量就是自動變量,也可使用auto顯示定義。靜態(tài)變量:是指內(nèi)存位置在程序執(zhí)行期間一直不改變的變量,用關(guān)鍵字static修飾。代碼塊內(nèi)部的靜態(tài)變量只能被這個代碼塊內(nèi)部訪問,代碼塊外部的靜態(tài)變量只能被定義這個變量的文件訪問。注意:extern修飾變量時,根據(jù)具體情況,既可以看作是定義也可以看作是聲明;但extern修飾函數(shù)時只能是定義,沒有二義性。

          • 2.作用域:通常指的是變量的作用域,廣義上講,也有函數(shù)作用域及文件作用域等。我理解的作用域就是指某個事物能夠存在的區(qū)域或范圍,比如一滴水只有在0-100攝氏度之間才能存在,超出這個范圍,廣義上講的“水”就不存在了,它就變成了冰或氣體。

          • 3.函數(shù):不解釋。

          注意:C語言中函數(shù)默認(rèn)都是全局的,可以使用static關(guān)鍵字將函數(shù)聲明為靜態(tài)函數(shù)(只能被定義這個函數(shù)的文件訪問的函數(shù))。

          二、內(nèi)存四區(qū)

          計算機(jī)中的內(nèi)存是分區(qū)來管理的,程序和程序之間的內(nèi)存是獨立的,不能互相訪問,比如QQ和瀏覽器分別所占的內(nèi)存區(qū)域是不能相互訪問的。而每個程序的內(nèi)存也是分區(qū)管理的,一個應(yīng)用程序所占的內(nèi)存可以分為很多個區(qū)域,我們需要了解的主要有四個區(qū)域,通常叫內(nèi)存四區(qū),如下圖:

          image

          1.代碼區(qū)

          程序被操作系統(tǒng)加載到內(nèi)存的時候,所有的可執(zhí)行代碼(程序代碼指令、常量字符串等)都加載到代碼區(qū),這塊內(nèi)存在程序運行期間是不變的。代碼區(qū)是平行的,里面裝的就是一堆指令,在程序運行期間是不能改變的。函數(shù)也是代碼的一部分,故函數(shù)都被放在代碼區(qū),包括main函數(shù)。

          注意:"int a = 0;"語句可拆分成"int a;"和"a = 0",定義變量a的"int a;"語句并不是代碼,它在程序編譯時就執(zhí)行了,并沒有放到代碼區(qū),放到代碼區(qū)的只有"a = 0"這句。

          2.靜態(tài)區(qū)

          靜態(tài)區(qū)存放程序中所有的全局變量和靜態(tài)變量。

          3.棧區(qū)

          棧(stack)是一種先進(jìn)后出的內(nèi)存結(jié)構(gòu),所有的自動變量、函數(shù)形參都存儲在棧中,這個動作由編譯器自動完成,我們寫程序時不需要考慮。棧區(qū)在程序運行期間是可以隨時修改的。當(dāng)一個自動變量超出其作用域時,自動從棧中彈出。

          每個線程都有自己專屬的棧;棧的最大尺寸固定,超出則引起棧溢出;變量離開作用域后棧上的內(nèi)存會自動釋放?! alk is cheap, show you the code:

          //實驗一:觀察代碼區(qū)、靜態(tài)區(qū)、棧區(qū)的內(nèi)存地址

          #include?"stdafx.h"
          int?n?=?0;
          void?test(int?a,?int?b)
          {
          printf("形式參數(shù)a的地址是:%d\n形式參數(shù)b的地址是:%d\n",&a,?&b);
          }
          int?_tmain(int?argc,?_TCHAR*?argv[])
          {
          static?int?m?=?0;
          int?a?=?0;
          int?b?=?0;
          printf("自動變量a的地址是:%d\n自動變量b的地址是:%d\n",?&a,?&b);
          printf("全局變量n的地址是:%d\n靜態(tài)變量m的地址是:%d\n",?&n,?&m);
          test(a,?b);
          printf("_tmain函數(shù)的地址是:%d",?&_tmain);
          getchar();
          }

          運行結(jié)果如下:

          image

          結(jié)果分析:自動變量a和b依次被定義和賦值,都在棧區(qū)存放,內(nèi)存地址只相差12,需要注意的是a的地址比b要大,這是因為棧是一種先進(jìn)后出的數(shù)據(jù)存儲結(jié)構(gòu),先存放的a,后存放的b,形象化表示如上圖(注意地址編號順序)。一旦超出作用域,那么變量b將先于變量a被銷毀。這很像往箱子里放衣服,最先放的最后才能被拿出,最后放的最先被拿出。

          關(guān)注公眾號:C語言中文社區(qū),免費領(lǐng)取300G編程資料

          //實驗二:棧變量與作用域
          #include?"stdafx.h"
          //函數(shù)的返回值是一個指針,盡管這樣可以運行程序,但這樣做是不合法的,因為
          //非要這樣做需在x變量前加static關(guān)鍵字修飾,即static?int?a?=?0;
          int?*getx()
          {
          ????int?x?=?10;
          ????return?&x;
          }

          int?_tmain(int?argc,?_TCHAR*?argv[])
          {
          ????int?*p?=?getx();
          ????*p?=?20;
          ????printf("%d",?*p);
          ????getchar();
          }

          這段代碼沒有任何語法錯誤,也能得到預(yù)期的結(jié)果:20。但是這么寫是有問題的:因為int p = getx()中變量x的作用域為getx()函數(shù)體內(nèi)部,這里得到一個臨時棧變量x的地址,getx()函數(shù)調(diào)用結(jié)束后這個地址就無效了,但是后面的p = 20仍然在對其進(jìn)行訪問并修改,結(jié)果可能對也可能錯,實際工作中應(yīng)避免這種做法,不然怎么死的都不知道。不能將一個棧變量的地址通過函數(shù)的返回值返回,切記!

          另外,棧不會很大,一般都是以K為單位。如果在程序中直接將較大的數(shù)組保存在函數(shù)內(nèi)的棧變量中,很可能會內(nèi)存溢出,導(dǎo)致程序崩潰(如下實驗三),嚴(yán)格來說應(yīng)該叫棧溢出(當(dāng)??臻g以滿,但還往棧內(nèi)存壓變量,這個就叫棧溢出)。

          //實驗三:看看什么是棧溢出
          int?_tmain(int?argc,?_TCHAR*?argv[])
          {
          ????char?array_char[1024*1024*1024]?=?{0};
          ????array_char[0]?=?'a';
          ????printf("%s",?array_char);
          ????getchar();
          }
          image

          怎么辦?這個時候就該堆出場了。

          4.堆區(qū)

          堆(heap)和棧一樣,也是一種在程序運行過程中可以隨時修改的內(nèi)存區(qū)域,但沒有棧那樣先進(jìn)后出的順序。更重要的是堆是一個大容器,它的容量要遠(yuǎn)遠(yuǎn)大于棧,這可以解決上面實驗三造成的內(nèi)存溢出困難。一般比較復(fù)雜的數(shù)據(jù)類型都是放在堆中。但是在C語言中,堆內(nèi)存空間的申請和釋放需要手動通過代碼來完成。對于一個32位操作系統(tǒng),最大管理管理4G內(nèi)存,其中1G是給操作系統(tǒng)自己用的,剩下的3G都是給用戶程序,一個用戶程序理論上可以使用3G的內(nèi)存空間。堆上的內(nèi)存必須手動釋放(C/C++),除非語言執(zhí)行環(huán)境支持GC(如C#在.NET上運行就有垃圾回收機(jī)制)。那堆內(nèi)存如何使用?

          接下來看堆內(nèi)存的分配和釋放:

          malloc與free

          malloc函數(shù)用來在堆中分配指定大小的內(nèi)存,單位為字節(jié)(Byte),函數(shù)返回void *指針;free負(fù)責(zé)在堆中釋放malloc分配的內(nèi)存。malloc與free一定成對使用??聪旅娴睦樱?/p>

          //實驗四:解決棧溢出的問題
          #include?"stdafx.h"
          #include?"stdlib.h"
          #include?"string.h"

          void?print_array(char?*p,?char?n)
          {
          ????int?i?=?0;
          ????for?(i?=?0;?i?????{
          ????????printf("p[%d]?=?%d\n",?i,?p[i]);
          ????}
          }

          int?_tmain(int?argc,?_TCHAR*?argv[])
          {
          ????char?*p?=?(char?*)malloc(1024*1024*1024);//在堆中申請了內(nèi)存
          ????memset(p,?'a',?sizeof(int)?*?10);//初始化內(nèi)存
          ????int?i?=?0;
          ????for?(i?=?0;?i?10;?i++)
          ????{
          ????????p[i]?=?i?+?65;
          ????}
          ????print_array(p,?10);
          ????free(p);//釋放申請的堆內(nèi)存
          ????getchar();
          }

          運行結(jié)果為:

          image

          程序可以正常運行,這樣就解決了剛才實驗三的棧溢出問題。堆的容量有多大?理論上講,它可以使用除了系統(tǒng)占用內(nèi)存空間之外的所有空間。實際上比這要小些,比如我們平時會打開諸如QQ、瀏覽器之類的軟件,但這在一般情況下足夠用了。實驗二中說到,不能將一個棧變量的地址通過函數(shù)的返回值返回,如果我們需要返回一個函數(shù)內(nèi)定義的變量的地址該怎么辦?可以這樣做:

          //實驗五:
          #include?"stdafx.h"
          #include?"stdlib.h"

          int?*getx()
          {
          ????int?*p?=?(int?*)malloc(sizeof(int));//申請了一個堆空間
          ????return?p;
          }

          int?_tmain(int?argc,?_TCHAR*?argv[])
          {
          ????int?*pp?=?getx();
          ????*pp?=?10;
          ????free(pp);
          }

          這樣寫是沒有問題的,可以通過函數(shù)返回一個堆地址,但記得一定用通過free函數(shù)釋放申請的堆內(nèi)存空間。"int *p = (int *)malloc(sizeof(int));"換成"static int a = 0"也是合法的。因為靜態(tài)區(qū)的內(nèi)存在程序運行的整個期間都有效,但是后面的free函數(shù)就不能用了!

          用來在堆中申請內(nèi)存空間的函數(shù)還有calloc和realloc,用法與malloc類似。

          三、案例分析 案例一

          image

          部分分析如下:

          main函數(shù)和UpdateCounter為代碼的一部分,故存放在代碼區(qū)

          數(shù)組a默認(rèn)為全局變量,故存放在靜態(tài)區(qū)

          main函數(shù)中的"char *b = NULL"定義了自動變量b(variable),故其存放在棧區(qū)

          接著"b = (char?)malloc(1024sizeof(char));"向堆申請了部分內(nèi)存空間,故這段空間在堆區(qū)

          案例二

          image

          需要注意以下幾點:

          • 棧是從高地址向低地址方向增長;
          • 在C語言中,函數(shù)參數(shù)的入棧順序是從右到左,因此UpdateCounter函數(shù)的3個參數(shù)入棧順序是a1、c、b;
          • C語言中形參和實參之間是值傳遞,UpdateCounter函數(shù)里的參數(shù)a[1]、c、b與靜態(tài)區(qū)的a[1]、c、b不是同一個;

          "char *b = NULL"定義一個指針變量b,b的地址是0xFFF8,值為空-->運行到"b = (char*)malloc(1024*sizeof(char))"時才在堆中申請了一塊內(nèi)存(假設(shè)這塊內(nèi)存地址為0x77a0080)給了b,此時b的地址并沒有變化,但其值變?yōu)榱?x77a0080,這個值指向了一個堆空間的地址(棧變量的值指向了堆空間),這個過程b的內(nèi)存變化如下:

          image

          四、學(xué)習(xí)內(nèi)存管理的目的

          學(xué)習(xí)內(nèi)存管理就是為了知道日后怎么樣在合適的時候管理我們的內(nèi)存。那么問題來了?什么時候用堆什么時候用棧呢?一般遵循以下三個原則:

          • 如果明確知道數(shù)據(jù)占用多少內(nèi)存,那么數(shù)據(jù)量較小時用棧,較大時用堆;
          • 如果不知道數(shù)據(jù)量大小(可能需要占用較大內(nèi)存),最好用堆(因為這樣保險些);
          • 如果需要動態(tài)創(chuàng)建數(shù)組,則用堆。
          //實驗六:動態(tài)創(chuàng)建數(shù)組
          int?_tmain(int?argc,?_TCHAR*?argv[])
          {
          ????int?i;
          ????scanf("%d",?&i);
          ????int?*array?=?(int?*)malloc(sizeof(int)?*?i);
          ????//...//這里對動態(tài)創(chuàng)建的數(shù)組做其他操作
          ????free(array);
          }

          最后的最后

          操作系統(tǒng)在管理內(nèi)存時,最小單位不是字節(jié),而是內(nèi)存頁(32位操作系統(tǒng)的內(nèi)存頁一般是4K)。比如,初次申請1K內(nèi)存,操作系統(tǒng)會分配1個內(nèi)存頁,也就是4K內(nèi)存。4K是一個折中的選擇,因為:內(nèi)存頁越大,內(nèi)存浪費越多,但操作系統(tǒng)內(nèi)存調(diào)度效率高,不用頻繁分配和釋放內(nèi)存;內(nèi)存頁越小,內(nèi)存浪費越少,但操作系統(tǒng)內(nèi)存調(diào)度效率低,需要頻繁分配和釋放內(nèi)存。嵌入式系統(tǒng)的內(nèi)存內(nèi)存資源很稀缺,其內(nèi)存頁會更小,因此在嵌入式開發(fā)當(dāng)中需要特別注意。

          --- EOF ---
          版權(quán)歸原作者所有,如有侵權(quán),請聯(lián)系刪除。

          好文章點贊、在看和分享一條龍吧??
          瀏覽 104
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  小日本一级黄色电影 | 天天色天| 国产在线小视频 | 特黄特色A | 97超碰人人操人人 |