相信你們對內存對齊這個概念必定都比較熟悉,本文將介紹,如何利用內存對齊這一特性來作一些有意思的探索。html
至於爲何要使用內存對齊,這是一個比較複雜的問題,簡單來講就是提升cpu access memory的性能,後續有時間就內存對齊這個問題,展開詳細的探討。linux
首先來看一個簡單的示例:算法
假設咱們如今要用c語言作一個簡單的學生信息管理系統,學生結構體有三個基本屬性,分別是年齡(0-100),性別(male:0, female:1),姓名(字符串大小10之內)。在編碼以前,咱們須要對系統進行設計,而設計階段最重要的莫過於數據結構。本題涉及的結構體很是簡單,結構體student定義以下:編程
struct student { char age; char sex; char *name; };
相信上面這個結構體是大多數人得出的結果,那麼這個結構體的定義是否是最優的呢或者說是內存利用率是最高的呢?數組
在具體的探討以前,咱們先來介紹一下關於內存對齊的一個小知識點:若是某變量內存地址4字節對齊,則該地址的低2位必爲0。這個應該比較好理解,由於4字節對齊,內存地址必須爲4的倍數,因此低2位必然爲0,不然不能知足要求。數據結構
在瞭解這個知識點以後,咱們再來對上面的student結構體作一點修改。性能
咱們定義一個字符數組name用來存放學生姓名,且該結構體4字節對齊,定義以下:優化
char name[10] __attribute__ ((aligned(4))) = "hellooooo";
從上面的知識點,咱們知道字符數組name的低兩位爲0,換句話說,這兩位是沒有用到的,既然如此,咱們是否能夠考慮利用這兩位來作一些文章呢?編碼
咱們對上面的student結構體作以下修改:spa
struct student{ char age; unsigned long name_sex; };
咱們將sex和name字段合二爲一,用一個字段name_sex來表示,這樣作是否可行呢?
答案是可行的。
#define stu_get_name(stu) ((char *)((stu.name_sex) & ~3)) #define stu_get_sex(stu) ((stu.name_sex) & 1) char name[10] __attribute__ ((aligned(4))) = "hellooooo"; struct student stu; stu.age = 10; stu.name_sex = (unsigned long)name | 1; printf("name: %s \n", stu_get_name(stu)); printf("sex: %d \n", stu_get_sex(stu));
咱們先定義了一個字符數組name且該數組內存地址4字節對齊,即低兩位爲0。接着咱們將該地址的第0位置1用來保存學生性別字段,而後賦值爲student結構體的name_sex字段。
那麼咱們如何獲得student結構體的name字段的值呢?答案很簡單,只須要將name_sex字段的低兩位置0就能夠獲得咱們所須要的name字段值,而name_sex的第0位即((stu.name_sex) & 1)就是咱們student結構體中的sex字段,上述示例中,sex值爲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 的宏實現的。其實這裏還存在邏輯,但邏輯中存在一個對齊的數值定義。 不一樣過多介意宏裏面有宏,實際編譯,這些東西都會被優化掉。但對代碼組織,是有很大幫助的。哈
除非是模板,不然類的化,會固化方法。這對邏輯的鬆耦合不能帶來任何好處。設計,有時須要緊耦合,有時須要鬆耦合,其實判斷他們該鬆仍是緊,要根據這個設計的來源是否存在關聯斷定。好比,數據的邏輯提取和實際數據的存儲,一個來源業務要求的算法,一個來源於業務所運行的系統,所以須要鬆耦合,而在一個算法中的邏輯設計,則存在緊耦合。哈。這塊,比較繞口令,須要實踐體會。
注4:後續仍是要對本文的示例作一些修改,本文的示例的確很不恰當,不過仍是可以清晰的表達個人意思;
注5:本文的評論也值得你們閱讀和思考,不少知識點要想完全的搞明白鬚要很是深厚的功底,面對別人的質疑你是否可以從原理上說明白,是一項挑戰;
【1】http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html
【2】http://stackoverflow.com/questions/381244/purpose-of-memory-alignment
【3】http://en.wikipedia.org/wiki/Data_structure_alignment
若是您對算法或編程感興趣,歡迎掃描下方二維碼並關注公衆號「算法與編程之美」,和您一塊兒探索算法和編程的神祕之處,給您不同的解題分析思路。