對ARM處理器的內(nèi)存對齊問題
可以對齊或不對齊的內(nèi)存訪問。對齊的內(nèi)存訪問發(fā)生時的數(shù)據(jù)都位于其自然大小邊界。例如,如果該數(shù)據(jù)類型的大小是4個字節(jié),那么它屬于被4整除的內(nèi)存地址是位于其自然大小邊界。未對齊的內(nèi)存訪問發(fā)生在所有其他情況下(在上面的例子中,內(nèi)存地址時,是不能被4整除)。ARM處理器的設(shè)計有效地訪問對齊的數(shù)據(jù)。在ARM處理器上試圖訪問未對齊的數(shù)據(jù)會導(dǎo)致不正確的數(shù)據(jù)或顯著的性能損失(這些不同的癥狀會在稍后討論)。與此相反,大多數(shù)CISC型處理器(即x86)的訪問未對齊的數(shù)據(jù)是無害的。這份文件將討論一些比較常見的方式,一個應(yīng)用程序可能會執(zhí)行未對齊的內(nèi)存訪問,并提供一些建議的解決方案,以避免這些問題, 。
癥狀
上述問題,適用于所有ARM架構(gòu)。然而,根據(jù)MMU(內(nèi)存管理單元)和操作系統(tǒng)支持的可用性,應(yīng)用程序可能會看到不同的行為在不同的平臺上。默認情況下,未對齊的內(nèi)存訪問不會被困住了,會導(dǎo)致不正確的數(shù)據(jù)。與功能的MMU的平臺上,但是,OS捕獲非對齊訪問,它在運行時進行糾正。其結(jié)果將是正確的數(shù)據(jù),但在10-20 CPU周期的成本。
常見原因
上述問題的類型轉(zhuǎn)換適用于所有ARM架構(gòu)。然而,根據(jù)MMU(內(nèi)存管理單元)和操作系統(tǒng)支持的可用性,應(yīng)用程序可能會看到不同的行為在不同的平臺上。默認情況下,未對齊的內(nèi)存訪問不會被困住了,會導(dǎo)致不正確的數(shù)據(jù)。與功能的MMU的平臺上,但是,OS捕獲非對齊訪問,它在運行時進行糾正。其結(jié)果將是正確的數(shù)據(jù),但在10-20 CPU周期的成本。
代碼:
void my_func(char *a) { int *b = (int *)a; DBGPRINTF("%d", *b); }
這個簡單的例子,可能會導(dǎo)致未對齊的內(nèi)存訪問,因為我們不能保證的char * a是一個4字節(jié)的邊界上對齊。只要有可能,應(yīng)避免這種類型的施放。
使用數(shù)據(jù)緩沖區(qū)
未對齊的內(nèi)存訪問的最常見的原因源于不正確地處理數(shù)據(jù)緩沖區(qū)。這些數(shù)據(jù)緩沖區(qū)可能包含任何數(shù)據(jù)從USB端口讀取,通過網(wǎng)絡(luò),或從一個文件中。這個數(shù)據(jù)是很常見的包裝,有沒有插入填充,以確保數(shù)據(jù)在緩沖區(qū)內(nèi)位于其自然大小邊界。在這個例子中,我們會考慮的情況下,從文件加載的Windows BMP和解析的頭。的Windows BMP文件包含一個頭的像素數(shù)據(jù)。的標(biāo)頭是由兩個結(jié)構(gòu):
代碼:
typedef PACKED struct { unsigned short int type; /* Magic identifier */ unsigned int size; /* File size in bytes */ unsigned short int reserved1, reserved2; unsigned int offset; /* Offset to image data, bytes */ } HEADER; typedef PACKED struct { unsigned int size; /* Header size in bytes */ int width,height; /* Width and height of image */ unsigned short int planes; /* Number of colour planes */ unsigned short int bits; /* Bits per pixel */ unsigned int compression; /* Compression type */ unsigned int imagesize; /* Image size in bytes */ int xresolution,yresolution; /* Pixels per meter */ unsigned int ncolours; /* Number of colours */ unsigned int importantcolours; /* Important colours */ } INFOHEADER;
請注意,在的HEADER和INFOHEADER結(jié)構(gòu)的大小,分別為14和40字節(jié)。讓我們假設(shè)我們要確定在運行時的圖像的寬度和高度。的代碼來訪問這些數(shù)據(jù)可能看起來像這樣:
代碼:
#define INFOHEADER_OFFSET (sizeof(HEADER)) #define WIDTH_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, width)) #define HEIGHT_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, height)) int imageWidth, imageHeight; void * fileBuf; pMe->mFile = IFILEMGR_OpenFile(pMe->mFileMgr, "test.bmp", _OFM_READ); if (pMe->mFile) { IFILE_GetInfo(pMe->mFile, &fileInfo); fileBuf = MALLOC(fileInfo.dwSize); if (fileBuf) { result = IFILE_Read(pMe->mFile, fileBuf, fileInfo.dwSize); if (result == fileInfo.dwSize) { imageWidth = *((uint32*)(((byte*)fileBuf) + WIDTH_OFFSET)); imageHeight = *((uint32*)(((byte*)fileBuf) + HEIGHT_OFFSET)); } } }
注意的寬度和高度的偏移量。因為他們屬于一個半字邊界上,以上述方式訪問這些值會導(dǎo)致未對齊的內(nèi)存訪問。下面列出的一些推薦的方法來避免這個問題。
推薦的解決方案
使用memcpy
我們的第一個選項是,只需執(zhí)行MEMCPY從緩沖區(qū)中的數(shù)據(jù)到本地變量:
代碼:
if (result == fileInfo.dwSize) { MEMCPY(&imageWidth, (((byte*)fileBuf)+WIDTH_OFFSET), sizeof(uint32)); MEMCPY(&imageHeight, (((byte*)fileBuf)+HEIGHT_OFFSET), sizeof(uint32)); }
其結(jié)果是,存儲器被字節(jié)逐字節(jié),避免任何疑問對準。
包裝的編譯器指令
或者,我們可以使用壓縮的編譯器指令允許使用指針,直接將我們需要的數(shù)據(jù),同時迫使編譯器來處理對齊問題。在BREW環(huán)境中,PACKED被定義如下:
代碼:
#ifdef __ARMCC_VERSION #define PACKED __packed #else #define PACKED #endif
包裝形式,通過指定一個指針,ARM編譯器將生成相應(yīng)的說明來正確地訪問內(nèi)存,無論對齊。修改后的版本,上面的例子中,使用PACKED指針,如下:
代碼:
#define INFOHEADER_OFFSET (sizeof(HEADER)) #define WIDTH_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, width)) #define HEIGHT_OFFSET (INFOHEADER_OFFSET + offsetof(INFOHEADER, height)) PACKED uint32 * pImageWidth; PACKED uint32 * pImageHeight; uint32 imageWidth, imageHeight; void * fileBuf; pMe->mFile = IFILEMGR_OpenFile(pMe->mFileMgr, "test.bmp", _OFM_READ); if (pMe->mFile) { IFILE_GetInfo(pMe->mFile, &fileInfo); fileBuf = MALLOC(fileInfo.dwSize); if (fileBuf) { result = IFILE_Read(pMe->mFile, fileBuf, fileInfo.dwSize); if (result == fileInfo.dwSize) { pImageWidth = (uint32*)(((byte*)fileBuf) + WIDTH_OFFSET); pImageHeight = (uint32*)(((byte*)fileBuf) + HEIGHT_OFFSET); imageWidth = *pImageWidth; imageHeight = *pImageHeight; } } }
雖然程序員通常會無法控制標(biāo)準化的數(shù)據(jù)格式,如BMP頭在上面的例子中,當(dāng)你定義自己的數(shù)據(jù)結(jié)構(gòu)應(yīng)確保奠定了良好的對齊方式中的數(shù)據(jù)定義對齊的數(shù)據(jù)結(jié)構(gòu)。下面的基本示例演示了這樣的原則:
代碼:
#ifdef __ARMCC_VERSION typedef PACKED struct { short a; // offsetof(a) = 0 int b; // offsetof(b) = 2 ? misalignment problem! short c; // offsetof(c) = 6 } BAD_STRUCT; typedef struct { int b; // offsetof(b) = 0 ? no problem! short a; // offsetof(a) = 4 short c; // offsetof(c) = 6 } GOOD_STRUCT;
通過簡單地重新排列中,我們聲明的結(jié)構(gòu)成員,我們可以解決一些對齊的問題。另外請注意,如果未聲明為包裝,BAD_STRUCT,編譯器通常會插入填充,每個字段對齊。然而,這通常是不希望的,因為它浪費內(nèi)存和避免幾乎總是可以簡單地通過聲明為了減小尺寸的字段。
BREW模擬器測試
BREW模擬器3.1.2及以上版本提供了能夠使數(shù)據(jù)對齊檢查。BREW模擬器啟用此功能時,將顯示一個對話框,通知您的每一個未對齊的內(nèi)存訪問,并為您提供的選項對這一問題視而不見,或闖入的代碼,請參閱BREW SDK用戶文檔一節(jié)揗isaligned數(shù)據(jù)異常支持更多信息,此功能。注:由于x86架構(gòu)的訪問未對齊的數(shù)據(jù)不會有任何問題,你可以不編譯模擬器的DLL使用__packed指令(PACKED這就是為什么在WIN32環(huán)境下的空白被定義為)。這意味著,通過使用PACKED指針的非對齊訪問,解決依舊會觸發(fā)在模擬器的對齊檢查。
評論