關於c語言記憶體位址對齊的一點思考

2021-09-29 18:24:15 字數 3162 閱讀 3458

至於為什麼要使用記憶體對齊,這是乙個比較複雜的問題,簡單來說就是提高cpu access memory的效能,後續有時間就記憶體對齊這個問題,展開詳細的**。

首先來看乙個簡單的示例:

假設我們現在要用c語言做乙個簡單的學生資訊管理系統,學生結構體有三個基本屬性,分別是年齡(0-100),性別(male:0, female:1),姓名(字串大小10以內)。在編碼之前,我們需要對系統進行設計,而設計階段最重要的莫過於資料結構。本題涉及的結構體非常簡單,結構體student定義如下:

struct student ;
相信上面這個結構體是大多數人得出的結果,那麼這個結構體的定義是不是最優的呢或者說是記憶體利用率是最高的呢?

在具體的**之前,我們先來介紹一下關於記憶體對齊的乙個小知識點:如果某變數記憶體位址4位元組對齊,則該位址的低2位必為0。這個應該比較好理解,因為4位元組對齊,記憶體位址必須為4的倍數,所以低2位必然為0,否則不能滿足要求。

在了解這個知識點之後,我們再來對上面的student結構體做一點修改。

我們定義乙個字元陣列name用來存放學生姓名,且該結構體4位元組對齊,定義如下:

char name[10] __attribute__ ((aligned(4))) = "hellooooo";
從上面的知識點,我們知道字元陣列name的低兩位為0,換句話說,這兩位是沒有用到的,既然如此,我們是否可以考慮利用這兩位來做一些文章呢?

我們對上面的student結構體做如下修改:

struct student;
我們將***和name欄位合二為一,用乙個欄位name_***來表示,這樣做是否可行呢?

答案是可行的。

#define stu_get_name(stu) ((char *)((stu.name_***) & ~3))

#define stu_get_***(stu) ((stu.name_***) & 1)

char name[10] __attribute__ ((aligned(4))) = "hellooooo";

struct student stu;

stu.age = 10;

stu.name_*** = (unsigned long)name | 1;

printf("name: %s \n", stu_get_name(stu));

printf("***: %d \n", stu_get_***(stu));

我們先定義了乙個字元陣列name且該陣列記憶體位址4位元組對齊,即低兩位為0。接著我們將該位址的第0位置1用來儲存學生性別字段,然後賦值為student結構體的name_***字段。

那麼我們如何得到student結構體的name欄位的值呢?答案很簡單,只需要將name_***欄位的低兩位置0就可以得到我們所需要的name字段值,而name_***的第0位即((stu.name_***) & 1)就是我們student結構體中的***字段,上述示例中,***值為1,即性別為female。

至此,我們利用記憶體位址對齊的特性,修改了我們示例最先提出的student結構體。

本文中我們利用4位元組記憶體對齊的低兩位為0這一特性,將其最低位用來存放學生性別,從而達到高效的利用記憶體。

本文的重點並不在於介紹如何設計乙個學生資訊管理系統,示例中的結構體只是為了說明記憶體對齊的應用,借助學生資訊管理系統這樣的乙個場景來介紹,我們在設計結構體的時候,利用記憶體對齊的特性,可以更加靈活的設計我們所需要的結構體,從而達到對記憶體的高效利用。

注1:如對記憶體對齊的應用感興趣,可進一步

參考linux核心中rbtree的設計,其rb_parent_color欄位就是利用了記憶體對齊的特性,將結點的父結點parent以及該結點的顏色color兩個字段合二為一。

注2:本空間《**思考》系列博文都是基於linux核心,用平實的語言和簡單的示例,描述linux核心中一些比較有意思的設計,希望能夠和大家一起探索linux核心設計的奧秘。

注3:@中山野鬼 老師的兩句點評非常精闢,受益匪淺,和大家一起分享下,前輩總是能夠一語道破個中玄機:

樓主記得,記憶體對齊的處理邏輯,一定要和計算邏輯分開。有關聯的地方使用巨集的方式就可以。否則以後你有苦頭吃。而且會額外增加計算邏輯的複雜度。
有些事情不是底層可以幫你更好的處理的。乙個簡單的例子,你去設計乙個資料結構,比如樹吧,對節點的訪問邏輯,一旦你固定,則不會有改變,但是每個節點的儲存空間的實際訪問,則會根據儲存方式的改變而改變,通常是用巨集的方式,進行調整。這樣的調整不會影響整體邏輯,但是會改變資料計算過程中,對資料訪問的儲存空間
所謂記憶體對其,其實和記憶體申請沒有關係,只是和具體物件(不是物件導向的物件)的定址有關係。比如,你要對乙個物件進行資料讀取或者寫入,你總是先要計算位址,然後進行訪問。 而計算位址是根據邏輯來的。通過計算位址進行直接儲存訪問,則存在乙個邏輯轉換,確保每個資料對齊。這裡增加個巨集,由此實現分離。 簡單的例子,我們邏輯上連續儲存24位畫素,假設(通常一行內不會如此)我們希望每個畫素的儲存是32位對齊。那麼你訪問每個畫素,存在(x,y,z)三個變數,x,y是乙個平面的列數,和行數,z是層級數。 假設b是基位址。則如下操作 #define image_pixel_byte_size 4 #define get_bias(x,y,z) ((z) * x * y + (y) * y + x) #define get_store(b,n) ((byte)b + n * image_pixel_byte_size) #define get_pixel(p,x,y,z) get_store(p,get_bias(x,y,z)) 上面,實際記憶體對齊操作,是通過 get_store 的巨集實現的。其實這裡還存在邏輯,但邏輯中存在乙個對齊的數值定義。 不同過多介意巨集裡面有巨集,實際編譯,這些東西都會被優化掉。但對**組織,是有很大幫助的。哈
除非是模板,否則類的化,會固化方法。這對邏輯的松耦合不能帶來任何好處。設計,有時需要緊耦合,有時需要松耦合,其實判斷他們該鬆還是緊,要根據這個設計的**是否存在關聯判定。比如,資料的邏輯提取和實際資料的儲存,乙個**業務要求的演算法,乙個**於業務所執行的系統,因此需要松耦合,而在乙個演算法中的邏輯設計,則存在緊耦合。哈。這塊,比較繞口令,需要實踐體會。

C語言記憶體位址對齊詳解

什麼是位址對齊?現代計算機中記憶體空間都是按照位元組 byte 劃分的,從理論上講似乎對任何型別的變數的訪問可以從任何位址開始,但實際情況是在訪問特定變數的時候經常在特定的記憶體位址訪問,這就需要各型別資料按照一定的規則在空間上排列,而不是順序的乙個接乙個的排列,這就是對齊。為什麼要位址對齊?對齊的...

C語言中的記憶體位址分配模型

img 說明 1 程式 區 存放函式體的二進位制 2 全域性區資料區 全域性資料區劃分為三個區域。全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。常量資料存放在另乙個區域裡。這些資料在程式結束後由系統釋放。我...

go語言關於切片型別記憶體位址的理解

學習go語言切片型別時遇到了這樣乙個問題。首先,在go中,切片型別的變數實際上存放的是乙個位址,該位址即為其引用的底層陣列的第乙個元素的位址,也可以說是這個陣列的位址。如圖所示,建立乙個名為s的切片 var s int變數s儲存在棧區,其位址為0x000050420,而其值並不是陣列 1,2,3 而...