溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C語言學(xué)習(xí)(01)——內(nèi)存這個大話題

發(fā)布時間:2020-08-02 11:27:53 來源:網(wǎng)絡(luò) 閱讀:5944 作者:三九感冒靈 欄目:編程語言

1.內(nèi)存這個大話題

1.1.程序運行為什么需要內(nèi)存

1.1.1、計算機程序運行的目的

計算機為什么需要編程?編程已經(jīng)編了很多年,已經(jīng)寫了很多程序,為什么還需要另外寫程序?計算機有這個新的程序到底為了什么?
程序的目的是為了去運行,程序運行是為了得到一定的結(jié)果。計算機就是用來計算的,所有的計算機程序其實都是在做計算。計算就是在計算數(shù)據(jù)。所以計算機程序中很重要的部分就是數(shù)據(jù)。
計算機程序 = 代碼 + 數(shù)據(jù) 計算機程序運行完得到一個結(jié)果,就是說
代碼 + 數(shù)據(jù) (經(jīng)過運行后) = 結(jié)果
從宏觀上來理解,代碼就是動作,就是加工數(shù)據(jù)的動作;數(shù)據(jù)就是數(shù)字,就是被代碼所加工的東西。
那么可以得出結(jié)論:程序運行的目的不外乎2個:結(jié)果、過程
用函數(shù)來類比:函數(shù)的形參就是待加工的數(shù)據(jù)(函數(shù)內(nèi)還需要一些臨時數(shù)據(jù),就是局部變量),函數(shù)本體就是代碼,函數(shù)的返回值就是結(jié)果,函數(shù)體的執(zhí)行過程就是過程。

    int add(int a, int b)
    {
        return a + b;
    }           // 這個函數(shù)的執(zhí)行就是為了得到結(jié)果
    void add(int a, int b)
    {
        int c;
        c = a + b;
        printf("c = %d.\n", c);
    }           // 這個函數(shù)的執(zhí)行重在過程(重在過程中的printf),返回值不需要
    int add(int a, int b)
    {
        int c;
        c = a + b;
        printf("c = %d.\n", c);
        return c;
    }           // 這個函數(shù)又重結(jié)果又重過程 

1.1.2、計算機程序運行過程

計算機程序的運行過程,其實就是程序中很多個函數(shù)相繼運行的過程。程序是由很多個函數(shù)組成的,程序的本質(zhì)就是函數(shù),函數(shù)的本質(zhì)是加工數(shù)據(jù)的動作,動作的操作對象是數(shù)據(jù)。

1.1.3、馮諾依曼結(jié)構(gòu)和哈佛結(jié)構(gòu)

  • 馮諾依曼結(jié)構(gòu)是:數(shù)據(jù)和代碼放在一起。
  • 哈佛結(jié)構(gòu)是:數(shù)據(jù)和代碼分開存在。
    什么是代碼:函數(shù)就是代碼
    什么是數(shù)據(jù):全局變量、局部變量,...
    在S5PV210中運行的linux系統(tǒng)上,運行應(yīng)用程序時:這時候所有的應(yīng)用程序的代碼和數(shù)據(jù)都在DRAM,所以這種結(jié)構(gòu)就是馮諾依曼結(jié)構(gòu);在單片機中,我們把程序代碼燒寫到Flash(NorFlash)中,然后程序在Flash中原地運行,程序中所涉及到的數(shù)據(jù)(全局變量、局部變量)不能放在Flash中,必須放在RAM(SRAM)中。這種就叫哈佛結(jié)構(gòu)。

    1.1.4、動態(tài)內(nèi)存DRAM和靜態(tài)內(nèi)存SRAM

    DRAM是動態(tài)內(nèi)存,SRAM是靜態(tài)內(nèi)存。詳細(xì)細(xì)節(jié)自己baidu

    1.1.5、總結(jié):為什么需要內(nèi)存呢

    內(nèi)存是用來存儲可變數(shù)據(jù)的,數(shù)據(jù)在程序中表現(xiàn)為全局變量、局部變量等(在gcc中,其實常量也是存儲在內(nèi)存中的)(大部分單片機中,常量是存儲在flash中的,也就是在代碼段),對我們寫程序來說非常重要,對程序運行更是本質(zhì)相關(guān)。
    所以內(nèi)存對程序來說幾乎是本質(zhì)需求。越簡單的程序需要越少的內(nèi)存,而越龐大越復(fù)雜的程序需要更多的內(nèi)存。內(nèi)存管理是我們寫程序時很重要的話題。我們以前學(xué)過的了解過的很多編程的關(guān)鍵其實都是為了內(nèi)存,譬如說數(shù)據(jù)結(jié)構(gòu)(數(shù)據(jù)結(jié)構(gòu)是研究數(shù)據(jù)如何組織的,數(shù)據(jù)是放在內(nèi)存中的)和算法(算法是為了用更優(yōu)秀更有效的方法來加工數(shù)據(jù),既然跟數(shù)據(jù)有關(guān)就離不開內(nèi)存)。

    1.1.6、深入思考:如何管理內(nèi)存(無OS時,有OS時)

    對于計算機來說,內(nèi)存容量越大則可能性越大,所以大家都希望自己的電腦內(nèi)存更大。我們寫程序時如何管理內(nèi)存就成了很大的問題。如果管理不善,可能會造成程序運行消耗過多的內(nèi)存,這樣遲早內(nèi)存都被你這個程序吃光了,當(dāng)沒有內(nèi)存可用時程序就會崩潰。所以內(nèi)存對程序來說是一種資源,所以管理內(nèi)存對程序來說是一個重要技術(shù)和話題。
    沒有操作系統(tǒng)時:在沒有操作系統(tǒng)(其實就是裸機程序)中,程序需要直接操作內(nèi)存,編程者需要自己計算內(nèi)存的使用和安排。如果編程者不小心把內(nèi)存用錯了,錯誤結(jié)果需要自己承擔(dān)

從操作系統(tǒng)角度講:
操作系統(tǒng)掌握所有的硬件內(nèi)存,因為內(nèi)存很大,所以操作系統(tǒng)把內(nèi)存分成1個1個的頁面(其實就是一塊,一般是4KB),然后以頁面為單位來管理。頁面內(nèi)用更細(xì)小的方式來以字節(jié)為單位管理。操作系統(tǒng)內(nèi)存管理的原理非常麻煩、非常復(fù)雜、非常不人性化。那么對我們這些使用操作系統(tǒng)的人來說,其實不需要了解這些細(xì)節(jié)。操作系統(tǒng)給我們提供了內(nèi)存管理的一些接口,我們只需要用API即可管理內(nèi)存。
譬如:在C語言中使用malloc free這些接口來管理內(nèi)存。

再從語言角度來講:不同的語言提供了不同的操作內(nèi)存的接口。
譬如:

  • 匯編:根本沒有任何內(nèi)存管理,內(nèi)存管理全靠程序員自己,匯編中操作內(nèi)存時直接使用內(nèi)存地址(譬如0xd0020010),非常麻煩;
  • C語言:C語言中編譯器幫我們管理直接內(nèi)存地址,我們都是通過編譯器提供的變量名等來訪問內(nèi)存的,操作系統(tǒng)下如果需要大塊內(nèi)存,可以通過API(malloc free)來訪問系統(tǒng)內(nèi)存。裸機程序中需要大塊的內(nèi)存需要自己來定義數(shù)組等來解決。
  • C++語言:C++語言對內(nèi)存的使用進(jìn)一步封裝。我們可以用new來創(chuàng)建對象(其實就是為對象分配內(nèi)存),然后使用完了用delete來刪除對象(其實就是釋放內(nèi)存)。所以C++語言對內(nèi)存的管理比C要高級一些,容易一些。但是C++中內(nèi)存的管理還是靠程序員自己來做。如果程序員new了一個對象,但是用完了忘記delete就會造成這個對象占用的內(nèi)存不能釋放,這就是內(nèi)存泄漏。
  • Java/C#等語言:這些語言不直接操作內(nèi)存,而是通過虛擬機來操作內(nèi)存。這樣虛擬機作為我們程序員的代理,來幫我們處理內(nèi)存的釋放工作。如果我的程序申請了內(nèi)存,使用完成后忘記釋放,則虛擬機會幫我釋放掉這些內(nèi)存。聽起來似乎C# java等語言比C/C++有優(yōu)勢,但是其實他這個虛擬機回收內(nèi)存是需要付出一定代價的,所以說語言沒有好壞,只有適應(yīng)不適應(yīng)。當(dāng)我們程序?qū)π阅芊浅T诤醯臅r候(譬如操作系統(tǒng)內(nèi)核)就會用C/C++語言;當(dāng)我們對開發(fā)程序的速度非常在乎的時候,就會用Java/C#等語言。

1.3.位、字節(jié)、半字、字的概念和內(nèi)存位寬

1.3.1、什么是內(nèi)存?(硬件和邏輯兩個角度)

  • 從硬件角度:內(nèi)存實際上是電腦的一個配件(一般叫內(nèi)存條)。根據(jù)不同的硬件實現(xiàn)原理還可以把內(nèi)存分成SRAM和DRAM(DRAM又有好多代,譬如最早的SDRAM,后來的DDR1、DDR2·····、LPDDR)
  • 從邏輯角度:內(nèi)存是這樣一種東西,它可以隨機訪問(隨機訪問的意思是只要給一個地址,就可以訪問這個內(nèi)存地址)、并且可以讀寫(當(dāng)然了邏輯上也可以限制其為只讀或者只寫);內(nèi)存在編程中天然是用來存放變量的(就是因為有了內(nèi)存,所以C語言才能定義變量,C語言中的一個變量實際就對應(yīng)內(nèi)存中的一個單元)。

    1.3.2、內(nèi)存的邏輯抽象圖(內(nèi)存的編程模型)

    從邏輯角度來講,內(nèi)存實際上是由無限多個內(nèi)存單元格組成的,每個單元格有一個固定的地址叫內(nèi)存地址,這個內(nèi)存地址和這個內(nèi)存單元格唯一對應(yīng)且永久綁定。
    以大樓來類比內(nèi)存是最合適的。邏輯上的內(nèi)存就好象是一棟無限大的大樓,內(nèi)存的單元格就好象大樓中的一個個小房間。每個內(nèi)存單元格的地址就好象每個小房間的房間號。內(nèi)存中存儲的內(nèi)容就好象住在房間中的人一樣。
    邏輯上來說,內(nèi)存可以有無限大(因為數(shù)學(xué)上編號永遠(yuǎn)可以增加,無盡頭)。但是現(xiàn)實中實際的內(nèi)存大小是有限制的,譬如32位的系統(tǒng)(32位系統(tǒng)指的是32位數(shù)據(jù)線,但是一般地址線也是32位,這個地址線32位決定了內(nèi)存地址只能有32位二進(jìn)制,所以邏輯上的大小為2的32次方)內(nèi)存限制就為4G。實際上32位的系統(tǒng)中可用的內(nèi)存是小于等于4G的(譬如我32位CPU裝32位windows,但實際電腦只有512M內(nèi)存)

    1.3.3、位和字節(jié)

    內(nèi)存單元的大小單位有4個:位(1bit) 字節(jié)(8bit) 半字(一般是16bit) 字(一般是32bit)
    在所有的計算機、所有的機器中(不管是32位系統(tǒng)還是16位系統(tǒng)還是以后的64位系統(tǒng)),位永遠(yuǎn)都是1bit,字節(jié)永遠(yuǎn)都是8bit。

    1.3.4、字和半字

    歷史上曾經(jīng)出現(xiàn)過16位系統(tǒng)、32位系統(tǒng)、64位系統(tǒng)三種,而且操作系統(tǒng)還有windows、linux、iOS等很多,所以很多的概念在歷史上曾經(jīng)被混亂的定義過。
    建議大家對字、半字、雙字這些概念不要詳細(xì)區(qū)分,只要知道這些單位具體有多少位是依賴于平臺的。實際工作中在每種平臺上先去搞清楚這個平臺的定義(字是多少位,半字永遠(yuǎn)是字的一半,雙字永遠(yuǎn)是字的2倍大?。?。
    編程時一般根本用不到字這個概念,那我們區(qū)分這個概念主要是因為有些文檔中會用到這些概念,如果不加區(qū)別可能會造成你對程序的誤解。
    在linux+ARM這個軟硬件平臺上,字是32位的。

    1.3.5、內(nèi)存位寬(硬件和邏輯兩個角度)

  • 從硬件角度講:硬件內(nèi)存的實現(xiàn)本身是有寬度的,也就是說有些內(nèi)存條就是8位的,而有些就是16位的。那么需要強調(diào)的是內(nèi)存芯片之間是可以并聯(lián)的,通過并聯(lián)后即使8位的內(nèi)存芯片也可以做出來16位或32位的硬件內(nèi)存。
  • 從邏輯角度講:內(nèi)存位寬在邏輯上是任意的,甚至邏輯上存在內(nèi)存位寬是24位的內(nèi)存(但是實際上這種硬件是買不到的,也沒有實際意義)。從邏輯角度來講不管內(nèi)存位寬是多少,我就直接操作即可,對我的操作不構(gòu)成影響。但是因為你的操作不是純邏輯而是需要硬件去執(zhí)行的,所以不能為所欲為,所以我們實際的很多操作都是受限于硬件的特性的。譬如24位的內(nèi)存邏輯上和32位的內(nèi)存沒有任何區(qū)別,但實際硬件都是32位的,都要按照32位硬件的特性和限制來干活。

    1.4.內(nèi)存編址和尋址、內(nèi)存對齊

    1.4.1、內(nèi)存編址方法

    A. 內(nèi)存在邏輯上就是一個一個的格子,這些格子可以用來裝東西(里面裝的東西就是內(nèi)存中存儲的數(shù)),每個格子有一個編號,這個編號就是內(nèi)存地址,這個內(nèi)存地址(一個數(shù)字)和這個格子的空間(實質(zhì)是一個空間)是一一對應(yīng)且永久綁定的。這就是內(nèi)存的編址方法。
    B. 在程序運行時,計算機中CPU實際只認(rèn)識內(nèi)存地址,而不關(guān)心這個地址所代表的空間在哪里,怎么分布這些實體問題。因為硬件設(shè)計保證了按照這個地址就一定能找到這個格子,所以說內(nèi)存單元的2個概念:地址和空間是內(nèi)存單元的兩個方面。

    1.4.2、關(guān)鍵:內(nèi)存編址是以字節(jié)為單位的

    我隨便給一個數(shù)字(譬如說7),然后說這個數(shù)字是一個內(nèi)存地址,然后問你這個內(nèi)存地址對應(yīng)的空間多大?這個大小是固定式,就是一個字節(jié)(8bit)。
    如果把內(nèi)存比喻位一棟大樓,那么這個樓里面的一個一個房間就是一個一個內(nèi)存格子,這個格子的大小是固定的8bit,就好象這個大樓里面所有的房間戶型是一樣的。

    1.4.3、內(nèi)存和數(shù)據(jù)類型的關(guān)系

    C語言中的基本數(shù)據(jù)類型有:char short int long float double
    int×××(整數(shù)類型,這個整就體現(xiàn)在它和CPU本身的數(shù)據(jù)位寬是一樣的)譬如32位的CPU,×××就是32位,int就是32位。
    數(shù)據(jù)類型和內(nèi)存的關(guān)系就在于:
    數(shù)據(jù)類型是用來定義變量的,而這些變量需要存儲、運算在內(nèi)存中。所以數(shù)據(jù)類型必須和內(nèi)存相匹配才能獲得最好的性能,否則可能不工作或者效率低下。
    在32位系統(tǒng)中定義變量最好用int,因為這樣效率高。原因就在于32位的系統(tǒng)本身配合內(nèi)存等也是32位,這樣的硬件配置天生適合定義32位的int類型變量,效率最高。也能定義8位的char類型變量或者16位的short類型變量,但是實際上訪問效率不高。
    在很多32位環(huán)境下,我們實際定義bool類型變量(實際只需要1個bit就夠了)都是用int來實現(xiàn)bool的。也就是說我們定義一個bool b1;時,編譯器實際幫我們分配了32位的內(nèi)存來存儲這個bool變量b1。編譯器這么做實際上浪費了31位的內(nèi)存,但是好處是效率高。
    問題:實際編程時要以省內(nèi)存為大還是要以運行效率為重?答案是不定的,看具體情況。很多年前內(nèi)存很貴機器上內(nèi)存都很少,那時候?qū)懘a以省內(nèi)存為主?,F(xiàn)在隨著半導(dǎo)體技術(shù)的發(fā)展內(nèi)存變得很便宜了,現(xiàn)在的機器都是高配,不在乎省一點內(nèi)存,而效率和用戶體驗變成了關(guān)鍵。所以現(xiàn)在寫程序大部分都是以效率為重。

    1.4.4、內(nèi)存對齊

    們在C中int a;定義一個int類型變量,在內(nèi)存中就必須分配4個字節(jié)來存儲這個a。有這么2種不同內(nèi)存分配思路和策略:

  • 第一種:0 1 2 3 對齊訪問
  • 第二種:1 2 3 4 或者 2 3 4 5 或者 3 4 5 6 非對齊訪問
    內(nèi)存的對齊訪問不是邏輯的問題,是硬件的問題。從硬件角度來說,32位的內(nèi)存它 0 1 2 3四個單元本身邏輯上就有相關(guān)性,這4個字節(jié)組合起來當(dāng)作一個int硬件上就是合適的,效率就高。
    對齊訪問很配合硬件,所以效率很高;非對齊訪問因為和硬件本身不搭配,所以效率不高。(因為兼容性的問題,一般硬件也都提供非對齊訪問,但是效率要低很多。)

    1.4.5、從內(nèi)存編址看數(shù)組的意義

    數(shù)組元素的變量名即就是數(shù)組的首地址(起始的第一個字節(jié)),首元素(起始的四字節(jié))

    1.5. C語言如何操作內(nèi)存

    1.5.1、C語言對內(nèi)存地址的封裝

    (用變量名來訪問內(nèi)存、數(shù)據(jù)類型的含義、函數(shù)名的含義)
    譬如在C語言中 int a; a = 5; a += 4; // a == 9;
    結(jié)合內(nèi)存來解析C語言語句的本質(zhì):
    int a; // 編譯器幫我們申請了1個int類型的內(nèi)存格子(長度是4字節(jié),地址是確定的,但是只有編譯器知道,我們是不知道的,也不需要知道。),并且把符號a和這個格子綁定。
    a = 5; // 編譯器發(fā)現(xiàn)我們要給a賦值,就會把這個值5丟到符號a綁定的那個內(nèi)存格子中。
    a += 4; // 編譯器發(fā)現(xiàn)我們要給a加值,a += 4 等效于 a = a + 4;編譯器會先把a原來的值讀出來,然后給這個值加4,再把加之后的和寫入a里面去。

    C語言中數(shù)據(jù)類型的本質(zhì)含義是:表示一個內(nèi)存格子的長度和解析方法。
    數(shù)據(jù)類型決定長度的含義:我們一個內(nèi)存地址(0x30000000),本來這個地址只代表1個字節(jié)的長度,但是實際上我們可以通過給他一個類型(int),讓他有了長度(4),這樣這個代表內(nèi)存地址的數(shù)字(0x30000000)就能表示從這個數(shù)字(0x30000000)開頭的連續(xù)的n(4)個字節(jié)的內(nèi)存格子了(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)。
    數(shù)據(jù)類型決定解析方法的含義:譬如我有一個內(nèi)存地址(0x30000000),我們可以通過給這個內(nèi)存地址不同的類型來指定這個內(nèi)存單元格子中二進(jìn)制數(shù)的解析方法(即讀取方法,譬如int類型就要按照int來讀和?。?。譬如我 (int)0x30000000,含義就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)這4個字節(jié)連起來共同存儲的是一個int型數(shù)據(jù);那么我(float)0x30000000,含義就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)這4個字節(jié)連起來共同存儲的是一個float型數(shù)據(jù);
    之前講過一個很重要的概念:內(nèi)存單元格子的編址單位是字節(jié)。

    (int *)0;
    (float *)0;
    (short)0;
    (char)0;
    
    int a;          // int a;時編譯器會自動給a分配一個內(nèi)存地址,譬如說是0x12345678
    (int *)a;       // 等價于(int *)0x12345678
    (float *)a;

C語言中,函數(shù)就是一段代碼的封裝。函數(shù)名的實質(zhì)就是這一段代碼的首地址。所以說函數(shù)名的本質(zhì)也是一個內(nèi)存地址。

1.5.2、用指針來間接訪問內(nèi)存

關(guān)于類型(不管是普通變量類型int float等,還是指針類型int float 等),只要記?。?br/>類型只是對后面數(shù)字或者符號(代表的是內(nèi)存地址)所表征的內(nèi)存的一種長度規(guī)定和解析方法規(guī)定而已。
C語言中的指針,全名叫指針變量,指針變量其實很普通變量沒有任何區(qū)別。譬如int a和int p其實沒有任何區(qū)別,a和p都代表一個內(nèi)存地址(譬如是0x20000000),但是這個內(nèi)存地址(0x20000000)的長度和解析方法不同。a是int型所以a的長度是4字節(jié),解析方法是按照int的規(guī)定來的;p是int 類型,所以長度是4字節(jié),解析方法是int *的規(guī)定來的(0x20000000開頭的連續(xù)4字節(jié)中存儲了1個地址,這個地址所代表的內(nèi)存單元中存放的是一個int類型的數(shù))。

1.5.3、指針類型的含義

1.5.4、用數(shù)組來管理內(nèi)存

數(shù)組管理內(nèi)存和變量其實沒有本質(zhì)區(qū)別,只是符號的解析方法不同。(普通變量、數(shù)組、指針變量其實都沒有本質(zhì)差別,都是對內(nèi)存地址的解析,只是解析方法不一樣)。
int a; // 編譯器分配4字節(jié)長度給a,并且把首地址和符號a綁定起來。
int b[10]; // 編譯器分配40個字節(jié)長度給b,并且把首元素首地址和符號b綁定起來。
數(shù)組中第一個元素(a[0])就稱為首元素;每一個元素類型都是int,所以長度都是4,其中第一個字節(jié)的地址就稱為首地址;首元素a[0]的首地址就稱為首元素首地址。

1.6.內(nèi)存管理之結(jié)構(gòu)體

1.6.1、數(shù)據(jù)結(jié)構(gòu)這門學(xué)問的意義

數(shù)據(jù)結(jié)構(gòu)就是研究數(shù)據(jù)如何組織(在內(nèi)存中排布),如何加工的學(xué)問。

1.6.2、最簡單的數(shù)據(jù)結(jié)構(gòu):數(shù)組

為什么要有數(shù)組?因為程序中有好多個類型相同、意義相關(guān)的變量需要管理,這時候如果用單獨的變量來做程序看起來比較亂,用數(shù)組來管理會更好管理。
譬如 int ages[20];

1.6.3、數(shù)組的優(yōu)勢和缺陷

優(yōu)勢:數(shù)組比較簡單,訪問用下標(biāo),可以隨機訪問。
缺陷:1 數(shù)組中所有元素類型必須相同;2 數(shù)組大小必須定義時給出,而且一旦確定不能再改。

1.6.4、結(jié)構(gòu)體隆重登場

結(jié)構(gòu)體發(fā)明出來就是為了解決數(shù)組的第一個缺陷:數(shù)組中所有元素類型必須相同
我們要管理3個學(xué)生的年齡(int類型),怎么辦?
第一種解法:用數(shù)組 int ages[3];
第二種解法:用結(jié)構(gòu)體

    struct ages
    {
        int age1;
        int age2;
        int age3;
    };
    struct ages age;

分析總結(jié):在這個示例中,數(shù)組要比結(jié)構(gòu)體好。但是不能得出結(jié)論說數(shù)組就比結(jié)構(gòu)體好,在包中元素類型不同時就只能用結(jié)構(gòu)體而不能用數(shù)組了。

    struct people
    {
        int age;            // 人的年齡
        char name[20];      // 人的姓名
        int height;         // 人的身高
    };
因為people的各個元素類型不完全相同,所以必須用結(jié)構(gòu)體,沒法用數(shù)組。

1.6.5、題外話:結(jié)構(gòu)體內(nèi)嵌指針實現(xiàn)面向?qū)ο?/h4>

面向過程與面向?qū)ο蟆?/p>

  • C語言是面向過程的,C++、Java等是面向?qū)ο蟮摹?/li>
  • 總的來說:C語言是面向過程的,但是C語言寫出的linux系統(tǒng)是面向?qū)ο蟮摹?/li>
  • 非面向?qū)ο蟮恼Z言,不一定不能實現(xiàn)面向?qū)ο蟮拇a。只是說用面向?qū)ο蟮恼Z言來實現(xiàn)面向?qū)ο笠雍唵我恍?、直觀一些、無腦一些。
  • 用C++、Java等面向?qū)ο蟮恼Z言來實現(xiàn)面向?qū)ο蠛唵我恍?,因為語言本身幫我們做了很多事情;但是用C來實現(xiàn)面向?qū)ο蠛苈闊?,看起來也不容易理解,這就是為什么大多數(shù)人學(xué)過C語言卻看不懂linux內(nèi)核代碼的原因。
    struct s
    {
    int age;                    // 普通變量
    void (*pFunc)(void);        // 函數(shù)指針,指向 void func(void)這類的函數(shù)
    };

    使用這樣的結(jié)構(gòu)體就可以實現(xiàn)面向?qū)ο蟆?br/>這樣包含了函數(shù)指針的結(jié)構(gòu)體就類似于面向?qū)ο笾械腸lass,結(jié)構(gòu)體中的變量類似于class中的成員變量,結(jié)構(gòu)體中的函數(shù)指針類似于class中的成員方法。

1.7、內(nèi)存管理之棧

1.7.1、什么是棧

棧是一種數(shù)據(jù)結(jié)構(gòu),C語言中使用棧來保存局部變量。棧是被發(fā)明出來管理內(nèi)存的。

4.7.2、棧管理內(nèi)存的特點(小內(nèi)存、自動化)

  • 先進(jìn)后出 FILO first in last out 棧
  • 先進(jìn)先出 FIFO first in first out 隊列
    棧的特點是入口即出口,只有一個口,另一個口是堵死的。所以先進(jìn)去的必須后出來。
    隊列的特點是入口和出口都有,必須從入口進(jìn)去,從出口出來,所以先進(jìn)去的必須先出來,否則就堵住后面的。

    4.7.3、棧的應(yīng)用舉例:局部變量

    C語言中的局部變量是用棧來實現(xiàn)的。
    我們在C中定義一個局部變量時(int a),編譯器會在棧中分配一段空間(4字節(jié))給這個局部變量用(分配時棧頂指針會移動給出空間,給局部變量a用的意思就是,將這4字節(jié)的棧內(nèi)存的內(nèi)存地址和我們定義的局部變量名a給關(guān)聯(lián)起來),對應(yīng)棧的操作是入棧。
    注意:這里棧指針的移動和內(nèi)存分配是自動的(棧自己完成,不用我們寫代碼去操作)。
    然后等我們函數(shù)退出的時候,局部變量要滅亡。對應(yīng)棧的操作是彈棧(出棧)。出棧時也是棧頂指針移動將??臻g中與a關(guān)聯(lián)的那4個字節(jié)空間釋放。這個動作也是自動的,也不用人寫代碼干預(yù)。

棧的優(yōu)點:棧管理內(nèi)存,好處是方便,分配和最后回收都不用程序員操心,C語言自動完成。
分析一個細(xì)節(jié):C語言中,定義局部變量時如果未初始化,則值是隨機的,為什么?
定義局部變量,其實就是在棧中通過移動棧指針來給程序提供一個內(nèi)存空間和這個局部變量名綁定。因為這段內(nèi)存空間在棧上,而棧內(nèi)存是反復(fù)使用的(臟的,上次用完沒清零的),所以說使用棧來實現(xiàn)的局部變量定義時如果不顯式初始化,值就是臟的,是隨機的。
如果你顯式初始化怎么樣?
C語言是通過一個小手段來實現(xiàn)局部變量的初始化的。
int a = 15; // 局部變量定義時初始化
C語言編譯器會自動把這行轉(zhuǎn)成:
int a; // 局部變量定義
a = 15; // 普通的賦值語句

4.7.4、棧的約束(預(yù)定棧大小不靈活,怕溢出)

首先,棧是有大小的。所以棧內(nèi)存大小不好設(shè)置。如果太小怕溢出,太大怕浪費內(nèi)存。(這個缺點有點像數(shù)組)
其次,棧的溢出危害很大,一定要避免。所以我們在C語言中定義局部變量時不能定義太多或者太大(譬如不能定義局部變量時 int a[10000]; 使用遞歸來解決問題時一定要注意遞歸收斂)

1.8、內(nèi)存管理之堆

1.8.1、什么是堆

堆(heap)是一種內(nèi)存管理方式。內(nèi)存管理對操作系統(tǒng)來說是一件非常復(fù)雜的事情,因為首先內(nèi)存容量很大,其次內(nèi)存需求在時間和大小塊上沒有規(guī)律(操作系統(tǒng)上運行著的幾十、幾百、幾千個進(jìn)程隨時都會申請或者釋放內(nèi)存,申請或者釋放的內(nèi)存塊大小隨意)。
堆這種內(nèi)存管理方式特點就是自由(隨時申請、釋放;大小塊隨意)。堆內(nèi)存是操作系統(tǒng)劃歸給堆管理器(操作系統(tǒng)中的一段代碼,屬于操作系統(tǒng)的內(nèi)存管理單元)來管理的,然后向使用者(用戶進(jìn)程)提供API(malloc和free)來使用堆內(nèi)存。
我們什么時候使用堆內(nèi)存?需要內(nèi)存容量比較大時,需要反復(fù)使用及釋放時,很多數(shù)據(jù)結(jié)構(gòu)(譬如鏈表)的實現(xiàn)都要使用堆內(nèi)存。

1.8.2、堆管理內(nèi)存的特點(大塊內(nèi)存、手工分配&使用&釋放)

特點:容量不限(常規(guī)使用的需求容量都能滿足)。
申請及釋放都需要手工進(jìn)行,手工進(jìn)行的含義就是需要程序員寫代碼明確進(jìn)行申請malloc及釋放free。如果程序員申請內(nèi)存并使用后未釋放,這段內(nèi)存就丟失了(在堆管理器的記錄中,這段內(nèi)存仍然屬于你這個進(jìn)程,但是進(jìn)程自己又以為這段內(nèi)存已經(jīng)不用了,再用的時候又會去申請新的內(nèi)存塊,這就叫吃內(nèi)存),稱為內(nèi)存泄漏。在C/C++語言中,內(nèi)存泄漏是最嚴(yán)重的程序bug,這也是別人認(rèn)為Java/C#等語言比C/C++優(yōu)秀的地方。

1.8.3、C語言操作堆內(nèi)存的接口(malloc free)

堆內(nèi)存釋放時最簡單,直接調(diào)用free釋放即可。 void free(void *ptr);
堆內(nèi)存申請時,有3個可選擇的類似功能的函數(shù):malloc, calloc, realloc
void *malloc(size_t size);
void *calloc(size_t nmemb, size_t size);    // nmemb個單元,每個單元size字節(jié)
void *realloc(void *ptr, size_t size);      // 改變原來申請的空間的大小的

譬如要申請10個int元素的內(nèi)存:
malloc(40);         malloc(10*sizeof(int));
calloc(10, 4);      calloc(10, sizeof(int));

數(shù)組定義時必須同時給出數(shù)組元素個數(shù)(數(shù)組大?。?,而且一旦定義再無法更改。在Java等高級語言中,有一些語法技巧可以更改數(shù)組大小,但其實這只是一種障眼法。它的工作原理是:先重新創(chuàng)建一個新的數(shù)組大小為要更改后的數(shù)組,然后將原數(shù)組的所有元素復(fù)制進(jìn)新的數(shù)組,然后釋放掉原數(shù)組,最后返回新的數(shù)組給用戶;

堆內(nèi)存申請時必須給定大小,然后一旦申請完成大小不變,如果要變只能通過realloc接口。realloc的實現(xiàn)原理類似于上面說的Java中的可變大小的數(shù)組的方式。

1.8.4、堆的優(yōu)勢和劣勢(管理大塊內(nèi)存、靈活、容易內(nèi)存泄漏)

  • 優(yōu)勢:靈活;
  • 劣勢:需要程序員去處理各種細(xì)節(jié),所以容易出錯,嚴(yán)重依賴于程序員的水平。
向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI