朱有鵬筆記 c語言內存

計算機程序 = 代碼 + 數據        計算機程序運行完得到一個結果,就是說
    代碼 + 數據 (經過運行後) = 結果
    從宏觀上來理解,代碼就是動作,就是加工數據的動作;數據就是數字,就是被代碼所加工的東西。
    那麼可以得出結論:程序運行的目的不外乎2個:結果、過程
    用函數來類比:函數的形參就是待加工的數據(函數內還需要一些臨時數據,就是局部變量),函數本體就是代碼,函數的返回值就是結果,函數體的執行過程就是過程。
    int add(int a, int b)
    {
        return a + b;
    }            // 這個函數的執行就是爲了得到結果
    void add(int a, int b)
    {
        int c;
        c = a + b;
        printf("c = %d.\n", c);
    }            // 這個函數的執行重在過程(重在過程中的printf),返回值不需要
    int add(int a, int b)
    {
        int c;
        c = a + b;
        printf("c = %d.\n", c);
        return c;
    }            // 這個函數又重結果又重過程

 

補充:形參

全稱爲“形式參數”是在定義函數名和函數體的時候使用的參數,目的是用來接收調用該函數時傳遞的參數。

形參的作用是實現主調函數與被調函數之間的聯繫,通常將函數所處理的數據,影響函數功能的因素或者函數處理的結果作爲形參。沒有形參的函數在形參表的位置應該寫void。main 函數也可以有形參和返回值,其形參也稱爲命令行參數,由操作系統在啓動程序時初始化,其返回值傳遞給操作系統。

函數

類型標識符 函數名 ( 形式參數列表 ) 

 語句部分 

形參和實參的特點

1、形參變量只有在被調用時才分配內存單元,在調用結束時,即刻釋放所分配的內存單元。因此,形參只在函數內部有效。函數調用結束返回主調用函數後則不能再使用該形參變量。

2、實參可以是常量、變量、表達式、函數等,無論實參是何種類型的量,在進行函數調用時,它們都必須有確定的值,以便把這些值傳送給形參。因此應預先用賦值,輸入等辦法使參數獲得確定值。

3、實參和形參在數量上,類型上、順序上應嚴格一致,否則就會發生類型不匹配的錯誤。

4、在一般傳值調用的機制中只能把實參傳送給形參,而不能把形參的值反向地傳送給實參。因此在函數調用過程中,形參值發生改變,而實參中的值不會變化。而在引用調用的機制當中是將實參引用的地址傳遞給了形參,所以任何發生在形參上的改變實際上也發生在實參變量上。

形參和實參的對照實例(C語言版)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

int main()

{

int n;

printf("input number\n");

scanf("%d",&n);

s(n);

printf("n=%d\n",n);

}

int s(int n)

{

int i;

for(i=n-1;i>=1;i--)

n=n+i;

printf("n=%d\n",n);

}

本程序中定義了一個函數 s,該函數的功能是求∑ni 的值。在主函數中輸入n 值,並作爲實參,在調用時傳送給s 函數的形參量n( 注意,本例的形參變量和實參變量的標識符都爲n,但這是兩個不同的量,各自的作用域不同)。在主函數中用printf 語句輸出一次n 值,這個n 值是實參n 的值。在函數s 中也用printf 語句輸出了一次n 值,這個n 值是形參最後取得的n 值。從運行情況看,輸入n 值爲100。即實參n 的值爲100。把此值傳給函數s 時,形參n 的初值也爲100,在執行函數過程中,形參n的值變爲5050。返回主函數之後,輸出實參n 的值仍爲100。可見實參的傳值調用值不隨形參的變化而變化。

5.當形參和實參不是指針類型時,在該函數運行時,形參和實參是不同的變量,他們在內存中位於不同的位置,形參將實參的內容複製一份,在該函數運行結束的時候形參被釋放,而實參內容不會改變。

而如果函數的參數是指針類型變量,在調用該函數的過程中,傳給函數的是實參的地址,在函數體內部使用的也是實參的地址,即使用的就是實參本身。所以在函數體內部可以改變實參的值。

形參改變實參的實例(C#語言版)

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

usingSystem;

usingSystem.Collections.Generic;

usingSystem.Linq;

usingSystem.Text;

namespaceConsoleApplication1

{

classBaseTest

{

///<summary>

///形參改變實參的值

///</summary>

///<paramname="strParameter">形參</param>

///<author>youngto</author>

public void TestChange(refstringstrParameter)

{

strParameter="changevalue";

Console.WriteLine(strParameter);

}

}

classProgram

{

staticvoidMain(string[]args)

{

stringstrArgument="defaultvalue";

Console.WriteLine(strArgumentt);//這裏實參的值沒改變。

BaseTestsm=newBaseTest();

sm.TestChange(refstrArgumentt);

Console.WriteLine(strArgumentt);//這裏實參的值改變。

}

}

}

輸出的值爲:

default value

change value

change value

在上面的"Main()”主 程序當中演示了實參"strArgument"的值在形參的作用域當中因形參"strParameter"的改變而改變了。(在C裏面 是無法做到形參改變 實參值同步改變的。
只能通過傳地址的方式
即參數類型爲指針
這樣 形參指向空間修改,可以使得實參指向空間同步修改,因爲是同一塊內存區域。

另外,在C++中,可以通過引用傳參,來實現目的。)

動態內存DRAM和靜態內存SRAM
    DRAM是動態內存,SRAM是靜態內存。詳細細節自己baidu

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

 

中央處理器的體系架構可以分爲:馮·諾依曼結構和哈佛結構

結構

使用馮·諾伊曼結構中央處理器和微控制器有很多。除了上面提到的英特爾公司的8086,英特爾公司的其他中央處理器、ARM的ARM7、MIPS公司MIPS處理器也採用了馮·諾依曼結構。

1945年,馮·諾依曼首先提出了“存儲程序”的概念和二進制原理,後來,人們把利用這種概念和原理設計的電子計算機系統統稱爲“馮·諾依曼型結構”計算機。馮·諾依曼結構的處理器使用同一個存儲器,經由同一個總線傳輸。

馮·諾曼結構處理器具有以下幾個特點:必須有一個存儲器;必須有一個控制器;必須有一個運算器,用於完成算術運算和邏輯運算;必須有輸入和輸出設備,用於進行人機通信。

哈佛結構

哈佛結構是一種將程序指令存儲和數據存儲分開的存儲器結構。中央處理器首先到程序指令存儲器中讀取程序指令內容,解碼後得到數據地址,再到相應的數據存儲器中讀取數據,並進行下一步的操作(通常是執行)。程序指令存儲和數據存儲分開,可以使指令和數據有不同的數據寬度,如Microchip公司的PIC16芯片的程序指令是14位寬度,而數據是8位寬度。

哈佛結構的微處理器通常具有較高的執行效率。其程序指令和數據指令分開組織和存儲的,執行時可以預先讀取下一條指令。使用哈佛結構的中央處理器和微控制器有很多,除了上面提到的Microchip公司的PIC系列芯片,還有摩托羅拉公司的MC68系列、Zilog公司的Z8系列、ATMEL公司的AVR系列和ARM公司的ARM9、ARM10和ARM11。

哈佛結構是指程序和數據空間獨立的體系結構,目的是爲了減輕程序運行時的訪存瓶頸。

例如最常見的卷積運算中, 一條指令同時取兩個操作數, 在流水線處理時, 同時還有一個取指操作,如果程序和數據通過一條總線訪問,取指和取數必會產生衝突,而這對大運算量的循環的執行效率是很不利的。哈佛結構能基本上解決取指和取數的衝突問題。而對另一個操作數的訪問,就只能採用Enhanced哈佛結構了,例如像TI那樣,數據區再split,並多一組總線。或向AD那樣,採用指令cache,指令區可存放一部分數據。

DSP算法中,最大量的工作之一是與存儲器交換信息,這其中包括作爲輸入信號的採樣數據、濾波器係數和程序指令。例如,如果將保存在存儲器中的2個數相乘,就需要從存儲器中取3個二進制數,即2個要乘的數和1個描述如何去做的程序指令。DSP內部一般採用的是哈佛結構,它在片內至少有4套總線:程序的數據總線,程序的地址總線,數據的數據總線和數據的地址總線。這種分離的程序總線和數據總線,可允許同時獲取指令字(來自程序存儲器)和操作數(來自數據存儲器),而互不干擾。這意味着在一個機器週期內可以同時準備好指令和操作數。有的DSP芯片內部還包含有其他總線,如DMA總線等,可實現單週期內完成更多的工作。這種多總線結構就好像在DSP內部架起了四通八達的高速公路,保障運算單元及時地取到需要的數據,提高運算速度。因此,對DSP來說,內部總線是個資源,總線越多,可以完成的功能就越複雜。超級哈佛結構(superHarvard architecture,縮寫爲SHARC),它在哈佛結構上增加了指令cache(緩存)和專用的I/O控制器。

哈佛結構處理器有兩個明顯的特點:使用兩個獨立的存儲器模塊,分別存儲指令和數據,每個存儲模塊都不允許指令和數據並存;使用獨立的兩條總線,分別作爲CPU與每個存儲器之間的專用通信路徑,而這兩條總線之間毫無關聯。

改進的哈佛結構,其結構特點爲:以便實現並行處理;具有一條獨立的地址總線和一條獨立的數據總線,利用公用地址總線訪問兩個存儲模塊(程序存儲模塊和數據存儲模塊),公用數據總線則被用來完成程序存儲模塊或數據存儲模塊與CPU之間的數據傳輸。

兩者區別

馮·諾依曼理論的要點是:數字計算機的數制採用二進制;計算機應該按照程序順序執行。人們把馮諾依曼的這個理論稱爲馮諾依曼體系結構。從ENIAC到當前最先進的計算機都採用的是馮諾依曼體系結構。所以馮諾依曼是當之無愧的數字計算機之父。

根據馮諾依曼體系結構構成的計算機,必須具有如下功能:把需要的程序和數據送至計算機中;必須具有長期記憶程序、數據、中間結果及最終運算結果的能力;能夠完成各種算術、邏輯運算和數據傳送等數據加工處理的能力;能夠根據需要控制程序走向,並能根據指令控制機器的各部件協調操作;能夠按照要求將處理結果輸出給用戶。

哈佛結構是爲了高速數據處理而採用的,因爲可以同時讀取指令和數據(分開存儲的)。大大提高了數據吞吐率,缺點是結構複雜。通用微機指令和數據是混合存儲的,結構上簡單,成本低。假設是哈佛結構:你就得在電腦安裝兩塊硬盤,一塊裝程序,一塊裝數據,內存裝兩根,一根儲存指令,一根存儲數據……

是什麼結構要看總線結構的。51單片機雖然數據指令存儲區是分開的,但總線是分時複用的,所以頂多算改進型的哈佛結構。ARM9雖然是哈佛結構,但是之前的版本也還是馮·諾依曼結構。早期的X86能迅速佔有市場,一條很重要的原因,正是靠了馮·諾依曼這種實現簡單,成本低的總線結構。處理器雖然外部總線上看是諾依曼結構的,但是由於內部CACHE的存在,因此實際上內部來看已經算是改進型哈佛結構的了。至於優缺點,哈佛結構就是複雜,對外圍設備的連接與處理要求高,十分不適合外圍存儲器的擴展。所以早期通用CPU難以採用這種結構。而單片機,由於內部集成了所需的存儲器,所以採用哈佛結構也未嘗不可。處理器,依託CACHE的存在,已經很好的將二者統一起來了。

內存是用來存儲可變數據的,數據在程序中表現爲全局變量、局部變量等(在gcc中,其實常量也是存儲在內存中的)(大部分單片機中,常量是存儲在flash中的,也就是在代碼段),對我們寫程序來說非常重要,對程序運行更是本質相關。    

所以內存對程序來說幾乎是本質需求。越簡單的程序需要越少的內存,而越龐大越複雜的程序需要更多的內存。內存管理是我們寫程序時很重要的話題。我們以前學過的瞭解過的很多編程的關鍵其實都是爲了內存,譬如說數據結構(數據結構是研究數據如何組織的,數據是放在內存中的)和算法(算法是爲了用更優秀更有效的方法來加工數據,既然跟數據有關就離不開內存)。

    對於計算機來說,內存容量越大則可能性越大,所以大家都希望自己的電腦內存更大。我們寫程序時如何管理內存就成了很大的問題。如果管理不善,可能會造成程序運行消耗過多的內存,這樣遲早內存都被你這個程序吃光了,當沒有內存可用時程序就會崩潰。所以內存對程序來說是一種資源,所以管理內存對程序來說是一個重要技術和話題。
    先從操作系統角度講:操作系統掌握所有的硬件內存,因爲內存很大,所以操作系統把內存分成1個1個的頁面(其實就是一塊,一般是4KB),然後以頁面爲單位來管理。頁面內用更細小的方式來以字節爲單位管理。操作系統內存管理的原理非常麻煩、非常複雜、非常不人性化。那麼對我們這些使用操作系統的人來說,其實不需要了解這些細節。操作系統給我們提供了內存管理的一些接口,我們只需要用API即可管理內存。
    譬如在C語言中使用malloc free這些接口來管理內存。
    沒有操作系統時:在沒有操作系統(其實就是裸機程序)中,程序需要直接操作內存,編程者需要自己計算內存的使用和安排。如果編程者不小心把內存用錯了,錯誤結果需要自己承擔。
    
    再從語言角度來講:不同的語言提供了不同的操作內存的接口。
    譬如彙編:根本沒有任何內存管理,內存管理全靠程序員自己,彙編中操作內存時直接使用內存地址(譬如0xd0020010),非常麻煩;
    譬如C語言:C語言中編譯器幫我們管理直接內存地址,我們都是通過編譯器提供的變量名等來訪問內存的,操作系統下如果需要大塊內存,可以通過API(malloc free)來訪問系統內存。裸機程序中需要大塊的內存需要自己來定義數組等來解決。

什麼是內存

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

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

 

內存和數據類型的關係

C語言中的基本數據類型有:char short int long float double 
    int 整形(整數類型,這個整就體現在它和CPU本身的數據位寬是一樣的)譬如32位的CPU,整形就是32位,int就是32位。
    數據類型和內存的關係就在於:
    數據類型是用來定義變量的,而這些變量需要存儲、運算在內存中。所以數據類型必須和內存相匹配才能獲得最好的性能,否則可能不工作或者效率低下。
    在32位系統中定義變量最好用int,因爲這樣效率高。原因就在於32位的系統本身配合內存等也是32位,
這樣的硬件配置天生適合定義32位的int類型變量,效率最高。也能定義8位的char類型變量或者16位的short類型變量,但是實際上訪問效率不高。

在很多32位環境下,我們實際定義bool類型變量(實際只需要1個bit就夠了)都是用int來實現bool的。也就是說我們定義一個bool b1;時,編譯器實際幫我們分配了32位的內存來存儲這個bool變量b1。編譯器這麼做實際上浪費了31位的內存,但是好處是效率高。

問題:實際編程時要以省內存爲大還是要以運行效率爲重?答案是不定的,看具體情況。很多年前內存很貴機器上內存都很少,那時候寫代碼以省內存爲主。現在隨着半導體技術的發展內存變得很便宜了,現在的機器都是高配,不在乎省一點內存,而效率和用戶體驗變成了關鍵。所以現在寫程序大部分都是以效率爲重。

C語言如何操作內存

C語言中數據類型的本質含義是:表示一個內存格子的長度和解析方法。

數據類型決定長度的含義:我們一個內存地址(0x30000000),本來這個地址只代表1個字節的長度,但是實際上我們可以通過給他一個類型(int),讓他有了長度(4),這樣這個代表內存地址的數字(0x30000000)就能表示從這個數字(0x30000000)開頭的連續的n(4)個字節的內存格子了(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)

數據類型決定解析方法的含義:譬如我有一個內存地址(0x30000000),我們可以通過給這個內存地址不同的類型來指定這個內存單元格子中二進制數的解析方法。譬如我 (int)0x30000000,含義就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)這4個字節連起來共同存儲的是一個int型數據;那麼我(float)0x30000000,含義就是(0x30000000 + 0x30000001 + 0x30000002 + 0x30000003)這4個字節連起來共同存儲的是一個float型數據;

C語言中,函數就是一段代碼的封裝。函數名的實質就是這一段代碼的首地址。所以說函數名的本質也是一個內存地址。

譬如int a和int *p其實沒有任何區別,a和p都代表一個內存地址(譬如是0x20000000),但是這個內存地址(0x20000000)的長度和解析方法不同。a是int型所以a的長度是4字節,解析方法是按照int的規定來的;p是int *類型,所以長度是4字節,解析方法是int *的規定來的(0x20000000開頭的連續4字節中存儲了1個地址,這個地址所代表的內存單元中存放的是一個int類型的數)。

指針大小爲什麼與類型無關

因爲它的大小與硬件有很大關係

相信這個問題很多像我一樣的新人都不知道。我們的內存中有各種各樣的數據,整型、浮點型、字符型等等。這些數據在內存中佔據不同大小的儲存空間,故用sizeof運算符(注:sizeof是種運算符而不是函數,它在編譯時發揮作用)進行運算時結果是不同的。然而不同類型的指針在相同系統環境下進行這種運算時結果卻是相同的。爲什麼呢?

衆所周知,C語言中的指針描述的是內存中的地址。而內存地址這種東西則是由CPU進行編址的。對於一個4位的CPU來講,它能同時輸出的數據爲4位,即0000-1111共2^4 種情況,故這些二進制數字只能對應到16個位置的內存地址,即CPU僅能識別出16個內存地址。即便你的內存再大,它也顯示只有16個位置的內存可用。這種原理同樣應用於32位和64位的CPU。

32位的CPU能同時呈現32個位的數據,故有2^32 種情況,對應到2^32 個內存位置也就是最大3.85GB大小,因此32位的系統只能支持最大4GB的內存。相比之下,64位的CPU能同時吞吐2^64 位的數據,這顯然能夠對應到2^64 個內存的地址,而理論上這個大小換算成10進制則是相當大的數,如果對應到內存,此時一個很大的內存。所以我們說64位系統理論支持無窮大內存(這裏的無窮大隻是一種概念,因爲我們不可能用到如此巨大容量的內存)。

綜上,因爲指針存放的是地址,所以32位內存,共4個字節;64位系統的64位地址共8個字節——你應該明白什麼了吧!沒錯,32位指針4字節,64位指針8字節。

當然,CPU只是影響指針大小的首要因素,除了它之外還要看操作系統和編譯器的位數。這裏指針的大小由這三個東西中位數最小的那項決定。比如,如果CPU、系統都是64位的,但編譯器是32位的,那麼很顯然指針只能是32位4字節大小。

 

指針的大小到底是由誰決定?是多少?

  搜了一下相關資料。。。居然發現回答不統一,很多人也同樣是糊里糊塗。

下面對這個問題做一個系統的整理和分析:

  首先,介紹幾個基本概念:(主要摘自百度百科)

  字長:在同一時間中處理二進制數的位數叫字長。通常稱處理字長爲8位數據的CPU叫8位CPU,32位CPU就是在同一時間內處理字長爲32位的二進制數據。二進制的每一個0或1是組成二進制的最小單位,稱爲一個比特(bit)。

   一般說來,計算機在同一時間內處理的一組二進制數稱爲一個計算機的“字”,而這組二進制數的位數就是“字長”。字長與計算機的功能和用途有很大的關係, 是計算機的一個重要技術指標。字長直接反映了一臺計算機的計算精度,爲適應不同的要求及協調運算精度和硬件造價間的關係,大多數計算機均支持變字長運算, 即機內可實現半字長、全字長(或單字長)和雙倍字長運算。在其他指標相同時,字長越大計算機的處理數據的速度就越快。早期的微機字長一般是8位和16 位,386以及更高的處理器大多是32位。目前市面上的計算機的處理器大部分已達到64位。

  字長由微處理器(CPU)對外數據通路的數據總線條數決定。

  最小可尋址單位:內存的最小可尋址單位通常都是字節。也就是說一個指針地址值可對應內存中一個字節的空間。

  尋址空間:尋址空間一般指的是CPU對於內存尋址的能力。CPU最大能查找多大範圍的地址叫做尋址能力 ,CPU的尋址能力以字節爲單位 (字節是最小可尋址單位),如32位尋址的CPU可以尋址2的32次方大小的地址也就是4G,這也是爲什麼32位尋址的CPU最大能搭配4G內存的原因 ,再多的話CPU就找不到了。

  這裏CPU的尋址位數是由地址總線的位數決定,32位CPU的尋址位數不一定是32位,因爲32位CPU中32的意義爲字長。

  有關尋址範圍計算解釋,對於32位尋址的CPU,其地址值爲32位的二進制數,所以可以表示的最大地址爲2的32次方(即4G,最大內存空間爲4GB,這裏G表示數量、GB表示容量)。同時我們不難看出,一個指針的值就是一個32位的二進制數,32位對應4字節(Byte)。  所以,指針的大小實際上是由CPU的尋址位數決定,而不是字長。

 

再來分析一下如下的情況:

  32位處理器上32位操作系統的32位編譯器,指針大小4字節。
  32位處理器上32位操作系統的16位編譯器,指針大小2字節。  
  32位處理器上16位操作系統的16位編譯器,指針大小2字節。
  16位處理器上16位操作系統的16位編譯器,指針大小2字節。

這從結果看起來指針的大小和編譯器有關??

  實際不是這樣的,有這樣的結果是因爲以上幾種情況,處理器當前運行模式的尋址位數是不一樣的,如下:

  Intel 32位處理器32位運行模式,邏輯尋址位數32,指針也就是32位,即4個字節
  Intel 32位處理器16位虛擬機運行模式,邏輯尋址位數16,指針也就是16位,即2個字節

  編譯器的作用是根據目標硬件(即CPU)的特性將源程序編譯爲可在該硬件上運行的目標文件。如果一個編譯器支持某32位的CPU,那麼它就可以將源程序編譯爲可以在該CPU上運行的目標文件。該源程序中指針大小也會被編譯器根據該CPU的尋址位數(如32位)編譯選擇爲4字節。

 

  綜上可得:指針大小是由當前CPU運行模式的尋址位數決定

 

用數組來管理內存
數組管理內存和變量其實沒有本質區別,只是符號的解析方法不同。(普通變量、數組、指針變量其實都沒有本質差別,都是對內存地址的解析,只是解析方法不一樣)。

 

來源:http://blog.sina.com.cn/s/blog_4fd9844201010n3v.html

https://blog.csdn.net/Jack_Lantern/article/details/52136133

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章