上一節我們討論了C語言程序中“內存對齊”的概念以及原因,其實歸根結底都是為了效率最大化,畢竟高效率是C語言程序的一個重要特性。
來看看這個面試題
瀏覽外文網站時,我發現了一個關於“內存對齊”的面試題目,原題主對這一個面試題完全沒有概念,即使谷歌之也沒有辦法。題目是這樣的:
memset_16aligned() 函數需要一個 16 字節對齊的指針作為參數,否則就會崩潰。a) 你能分配 1024 字節內存,並且將其 16 字節對齊嗎?b) 分配後,將其傳遞給 memset_16aligned() 函數執行後,釋放這段內存。{
void *mem;
void *ptr;
// answer a) here
memset_16aligned(ptr, 0, 1024);
// answer b) here
}
解析
題目要求傳遞給 memset_16aligned() 函數的 ptr 參數是 16 字節對齊的,這很好實現,只要保證 ptr 的地址值是 16 的整數倍就可以了。進一步分析問題,還能夠發現,ptr 應該指向一段 1024 字節的內存,因此相關的C語言代碼可以這樣寫:
{
void *mem = malloc(1024+16);
void *ptr = ((char *)mem+16) & ~(char *)0x0F;
memset_16aligned(ptr, 0, 1024);
free(mem);
}
第一步就是分配一塊足夠大的內存,由於內存必須是 16 字節對齊的,以防萬一,我們多分配了 16 字節,便於調整 ptr 指針的值。16 個連續數字裡,必定至少有一個數能夠被 16 整除,因此在前 16 個字節的某處,必定有一個 16 字節對齊的地址。
下一步是將 void 指針轉換為 char 指針,這是為了儘量避免對 void 指針指向指針算術。然後對轉換後的指針加上 16。
假設 malloc() 函數分配的內存起始地址為 0x800001,顯然未 16 字節對齊。現在對起始地址加 16,得到 0x800011,現在我想四捨五入到 16 字節邊界,所以可以將最後的 4 位置 0,也即 & ~0x0F。此時我們得到了 0x800010,這個地址顯然滿足 16 字節對齊。
讀者可以自己嘗試對其他地址執行上述操作,觀察是否能夠得到 16 字節對齊的地址。最後一步,釋放分配的內存很簡單,只需調用 free() 函數就可以了。但是應該注意,傳遞給 free() 函數的必須是 malloc() 函數返回的地址,也即 mem,而不能是 ptr,否則C語言程序就會崩潰。
題外話
可能有讀者知道自己使用的系統中 malloc() 函數的內部實現,可能它返回的地址必定是 16 字節對齊的(或者 8 字節對齊,4 字節對齊等等),那麼似乎就不再需要 ptr,直接使用 mem 就可以了。
然而,這是不可靠的也是不可移植的實現,因為其他平臺中的 malloc() 實現可能具有不同的最小對齊。作為C語言程序員,除非有某些限制,否則總是應該嘗試寫出可移植的C語言代碼。
其他問題
這個面試題目也引發了一些爭論,我覺得比較有意思的是這樣的一個觀點:“題目要求分配的是 [1024] 個字節!”也即這個觀點強調 1024 字節,而我們上面的C語言代碼分配的不止 1024 個字節。
其實,上面的C語言代碼是將題目理解成“分配一款足以容納 1024 字節數據的內存”了,如果面試官真的強調了 1024 字節,那麼問題就更加有趣了,我揣測此時面試官應該有兩種意思。
一是要求我們分配 1024 字節的內存,並對這段內存做 16 字節對齊處理。可是這樣我們最終得到的可用內存實際上是 1008 到 1024 之間的大小。
再就是要求我們自定義一個內存分配器,該內存分配器返回的內存起始地址必定是 16 字節對齊的。這樣一來,我們在內存分配器內部可能的實現就是基於上面的C語言代碼示例了,只不過,我們會把多出的字節隱藏在模塊內部了。
歡迎在評論區一起討論,質疑。文章都是手打原創,每天最淺顯的介紹C語言、linux等嵌入式開發,喜歡我的文章就關注一波吧,可以看到最新更新和之前的文章哦。
閱讀更多 IT劉小虎 的文章