淺析內存這個大話題

關鍵:內存編址是以字節爲單位的。

一.介紹內存

如果把內存比喻爲一棟大樓,那麼這個樓裏面的一個一個房間就是一個一個內存格子,這個格子的大小是固定的8bit,就好像這個大樓裏面所有的房間戶型是一樣的。
這裏寫圖片描述
數據類型和內存的關係就在於:
數據類型是用來定義變量的,而這些變量需要存儲、運算在內存中。所以數據類型必須和內存相匹配才能獲得最好的性能,否則可能不工作或者效率低下。

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

內存對齊:
C中定義int
定義一個int類型變量,在內存中就必須分配4個字節來存儲這個變量。有2種不同內存分配思路和策略:
第一種:0 1 2 3 對齊訪問
第二種:1 2 3 4 或 2 3 4 5 非對齊訪問
內存的對齊訪問不是邏輯的問題,是硬件的問題。從硬件角度來說,32位的內存它0 1 2 3四個單元本身邏輯上就有相關性,這4個字節組合起來當做一個int硬件上就是合適的,效率就高。

對齊訪問很配合硬件,所以效率很高;非對齊訪問因爲和硬件本身不搭配,所以效率不高。

二.普通數據類型

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

a地址爲0x30000000
(int )a;

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

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

內存格子的編址單位是字節

三.數據結構

結構體內嵌指針實現面向對象
面向過程和麪向對象
總的來說:C語言是面向過程的,但是C語言寫出的linux系統是面向對象的。
例如:
struct s
{
int age; //普通變量
void (*pFunc)(void); //函數指針,指向void func(void)這類函數

};
使用這樣的結構體就可以實現面向對象。
函數指針是一個變量,但指向一個函數,如同一個成員方法。
面向對象:類裏面有成員變量和成員方法(函數)。
這樣包含了函數指針的結構體就類似於面向對象中的class,結構體中的變量類似與class中的成員變量,結構體中的函數指針類似與class中的成員方法。

內存管理之棧(stack)
C語言中使用棧來保存局部變量。棧被髮明來管理內存的。

先進後出 FILO ——棧
先進先出 FIFO ——隊列
棧的特點是入口即出口,只有一個口,另一個口是堵死的。所以先進去的必須後出來
隊列的特點是入口和出口都有,必須從入口進去,從出口出來。所以先進去的必須先出來,否則堵住了。

C語言中的局部變量是棧實現的,我們在C中定義一個局部變量時(int a),編譯器會在棧中分配一段空間給這個局部變量用(分配時棧頂指針會移動給出空間,給局部變量a用的意思是,將這4個字節的棧內存的內存地址和我們定義的局部變量名a給關聯起來),對應棧的操作是入棧。
注意:這裏棧指針的移動和內存分配是自動的。
然後等我們函數退出的時候,局部變量要消亡。對應棧的操作是彈棧(出棧)。出棧時也是棧頂指針移動將棧空間中與a關聯的那4個字節空間釋放。這個動作是自動完成的。

棧的優點:棧管理內存,好處是方便,分配和回收不用程序員操心。

定義局部變量,其實就是在棧中通過移動棧指針來給程序提供一個內存空間和這個局部變量名綁定。因爲這段內存空間在棧上,而棧內存是反覆使用的,所以你不初始化使用棧的局部變量就是髒的。

棧的約束:
首先,棧是有大小的。所以棧內存大小不好設置。太小怕溢出,太大怕浪費。
棧的溢出危害很大。所以我們在C語言中定義局部變量時不能定義太大。
(使用遞歸解決問題一定要注意遞歸收斂)

內存管理之堆(heap)
堆是一種內存管理方式。內存管理對操作系統來說是一件非常複雜的事情。

堆這種內存管理方式的特點是自由(隨時申請,釋放。大小塊隨意)。堆內存是操作系統規劃給堆管理器(操作系統中的一段代碼,屬於操作系統的內存管理單元)來管理的,然後使用者(用戶進程)提供API(malloc和free)來使用堆內存。
我們什麼時候使用堆內存?需求內存容量比較大,需要反覆使用及釋放時。很多數據結構(譬如鏈表)的實現都要使用堆內存。
特點一:常規使用的需求容量都能滿足
特點二:申請及釋放都需要手工進行,手工進行的含義就是程序員自己寫代碼明確進行申請malloc及釋放free。如果程序員申請內存並使用後未釋放,這段內存就丟失了(在堆管理器的記錄中,這段內存仍屬於這個進程,但是進程自己又以爲這段內存已經不用了,再用的時候又會申請新的內存塊,就會吃內存),稱爲內存泄露。

堆內存申請時必須給定大小,然後一旦申請完成大小不變,如果要改變只能通過realloc接口。
void *realloc(void *ptr,size_t size)
ptr爲之前malloc返回的指針,size爲新分配的內存大小

堆的劣勢:需要程序員去處理各種細節,容易出錯,嚴重依賴程序員的水平。

複雜數據結構:
鏈表,哈希表,二叉樹,圖等
鏈表是最重要的,鏈表在Linux內核中使用非常多,驅動、應用編寫很多時候要使用鏈表。所以對鏈表必須掌握。

哈希表不是很常用,一般不需要自己實現,而直接使用別人實現的哈希表比較多。對我們來說要明白哈希表的原理、從而知道哈希表的特點,從而知道什麼時候改用哈希表,當看到別人用了哈希表的時候要明白別人爲什麼要用哈希表。

做內核、驅動,二叉樹和圖不需瞭解。

應該怎麼學習這部分?
1.數據結構和算法是相輔相成的,要一起研究。
2.數據結構和算法對嵌入式來說不全是重點,不要盲目的跑去研究這個。
3.一般在實際應用中,實現數據結構和算法的人和使用數據結構和算法的人是分開的。實際中有一部分人就是研究數據結構和算法,並且試圖用代碼來實現這些算法(表現爲庫);其他做真正工作的人要做的就是理解,明白這些算法和數據結構的意義、優劣、特徵,然後在合適的時候選擇合適的數據結構和算法來解決自己碰到的實際問題。

舉個例子:
linux內核在字符設備驅動管理時,使用了哈希表。所以字符設備的很多特點和哈希表的特點有關。

發佈了70 篇原創文章 · 獲贊 21 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章