關於記憶體對齊的話題,始終是敏感的。稍有不慎,必將闖下大禍!最近專案稍顯輕閒,自己給自己安排一天反思和總結一下,突然想到以前寫過的一篇'也談記憶體對齊
',那篇文章談的是記憶體對齊的基本知識以及一些實驗的資料,想必很多人看完後,會收穫一些東西,但是對記憶體對齊的應用還是處於懵懂狀態,其實大部分時間我們是不會顯式的用到'記憶體對齊的',但是有些時候我們需要這樣做。這裡做了乙個小例子,希望能給大家以啟發。
例子是這樣的:我們有一種二進位制檔案,其中儲存了多條經過特定對齊的某種記錄格式的資料,我們的任務就是解析出來這些資料,但是我們不知道也沒有這種資料的記錄格式結構的定義,但我們不是一無所有,我們有乙個表,這個表描述了這個記錄格式中有哪些域以及這些域的型別資訊,我們還知道的是源資料的對齊係數。
敘述完問題後,我們來給出一些具體的東西:
二進位制檔案生成程式:
#pragma pack(1)
struct foo_t ;
#pragma pack()
int main() ;
struct foo_t foo2 = ;
fp = fopen("foo.dat", "wb+");
if (fp == null)
fwrite(&foo1, sizeof(foo1), 1, fp);
fwrite(&foo2, sizeof(foo2), 1, fp);
fclose(fp);
return 0;
}生成的待解析檔案:foo.dat,其中有兩條記錄。
好了,我們的任務已經很明確了,就是正確解析出這兩條記錄。如果解析程式知道有下面這樣的結構體定義:
#pragma pack(1)
struct foo_t ;
#pragma pack()
那這裡也就不用說廢話了,我們不知道這個結構定義,不過我們通過知道的一些資訊可以整理出乙個描述該結構定義的乙個表:
#define x_char 1
#define x_string 2
#define x_int 3
typedef struct x_fld_info_t x_fld_info_t;
x_fld_info_t cpi_type_info_tab[3] = ,,};
想一想,我們能從檔案foo.dat中讀出來什麼?僅僅是一塊資料,每次讀多大一塊?如何在這塊資料中找到相應的域呢?沒錯,我們需要通過cpi_type_info_tab這個表資訊得出每條foo_t記錄的大小,還要得到foo_t中每個域在這塊資料中的偏移量,然後根據偏移量和域自身大小準確獲取其內容。
好了終於要用到記憶體對齊的知識了,其實想想也知道foo.dat的檔案生成程式和我們的解析程式可能不在一台機器上,而且完全可能在體系結構不同的機器上,這樣不同體系結構的機器他們的預設對齊係數、位元組序都可能不同(這裡我們暫不考慮位元組序的問題),我們在檔案生成程式那邊強制指定對齊係數有利於解析程式這邊的解析。我們要做的就是根據已知的對齊係數和cpi_type_info_tab表中的資訊計算出來該結構體在特定對齊係數下的總大小以及其各個域的偏移量。下面的巨集x_round_up和函式align_cpi_type配合完成了這一工作:
int x_atom_type_size[4] = ;
static int lg2(int k)
return i;
}#define x_round_up(x, k, rv) do while(0) /* 將x向上圓整到2的k次冪的倍數 */
static void align_cpi_type(x_fld_info_t *tab, int fld_cnt, int force_align_mod, int *size)
if (atom_sz < force_align_mod) else
x_round_up(cur, lg2(ali_mod), rv);
tab[i].offset = rv;
cur = tab[i].offset; /* 這一句**還要感謝一位留名為"十年草木"的網友的提醒 */
cur += (atom_sz * (tab[i].nitems));}/*
* 對齊整個復合型別
*/if (max_sz < force_align_mod) else
x_round_up(cur, lg2(ali_mod), rv);
(*size) = rv;
}如果對記憶體對齊還有疑惑的,可以去看看我的那篇'也談記憶體對齊
',再回到這來看align_cpi_type的實現,這裡的x_round_up的演算法借自於'hacker's delight
'一書,很好的一本討論'computer arithmetic'的書,裡面的很多knowledge & tip很有價值。通過align_cpi_type函式我們既得到了結構的大小也得到了結構中各個域的偏移量。根據這些資訊我們就可以輸出檔案foo.dat中的資料了。
static void output_cpi_mem(x_fld_info_t *tab, int fld_cnt, char *buf)
else if (tab[i].type == x_int) }}
int main() ,,};
int size = 0;
int i = 0;
align_cpi_type(cpi_type_info_tab, sizeof(cpi_type_info_tab)/sizeof(cpi_type_info_tab[0]), modules, &size);
/** 從檔案foo.dat中讀出所有記錄
* 並列印出來
*/file *fp = null;
char buf[50];
fp = fopen("foo.dat", "r");
if (fp == null)
for (i = 0; i < 2; i++)
fclose(fp);
return 0;
}執行輸出:
the value of field 'a' is [12457]
the value of field 'b' is [test foo1]
the value of field 'c' is [75421]
the value of field 'a' is [36098]
the value of field 'b' is [test foo2]
the value of field 'c' is [89063]
看到這有些人可能還是很糊塗,到底為什麼要這麼做呢?提示一下:現在我們要解析一儲存未知型別資料的檔案的記錄時,我們只需要這個紀錄的一些描述資訊即可了,而無需知道那個foo_t的具體定義了。能不能理解就看你自己了。
也談記憶體對齊
一 記憶體對齊的原因 大部分的參考資料都是如是說的 1 平台原因 移植原因 不是所有的硬體平台都能訪問任意位址上的任意資料的 某些硬體平台只能在某些位址處取某些特定型別的資料,否則丟擲硬體異常。2 效能原因 資料結構 尤其是棧 應該盡可能地在自然邊界上對齊。原因在於,為了訪問未對齊的記憶體,處理器需...
pragma pack n 也談記憶體對齊
在最近的專案中,我們涉及到了 記憶體對齊 技術。對於大部分程式設計師來說,記憶體對齊 對他們來說都應該是 透明的 記憶體對齊 應該是編譯器的 管轄範圍 編譯器為程式中的每個 資料單元 安排在適當的位置上。但是 c語言的乙個特點就是太靈活,太強大,它允許你干預 記憶體對齊 記憶體對齊 對你就不應該再透...
也談C 記憶體區域
眾所周知,c 記憶體區域被分為5大類 棧 堆 自由儲存區 全域性 靜態儲存區 常量儲存區。棧由編譯器控制,棧空間的申請 使用和釋放全權由編譯器處理。這裡的 全權處理 意思是責任歸屬,並不是說編譯器在程式執行時介入管理。實際上,編譯器的工作在編譯期就完成了,它對棧的管理體現在編譯時對暫存器esp的維護...