對(duì)ARM處理器的內(nèi)存對(duì)齊問題
可以對(duì)齊或不對(duì)齊的內(nèi)存訪問。對(duì)齊的內(nèi)存訪問發(fā)生時(shí)的數(shù)據(jù)都位于其自然大小邊界。例如,如果該數(shù)據(jù)類型的大小是4個(gè)字節(jié),那么它屬于被4整除的內(nèi)存地址是位于其自然大小邊界。未對(duì)齊的內(nèi)存訪問發(fā)生在所有其他情況下(在上面的例子中,內(nèi)存地址時(shí),是不能被4整除)。ARM處理器的設(shè)計(jì)有效地訪問對(duì)齊的數(shù)據(jù)。在ARM處理器上試圖訪問未對(duì)齊的數(shù)據(jù)會(huì)導(dǎo)致不正確的數(shù)據(jù)或顯著的性能損失(這些不同的癥狀會(huì)在稍后討論)。與此相反,大多數(shù)CISC型處理器(即x86)的訪問未對(duì)齊的數(shù)據(jù)是無害的。這份文件將討論一些比較常見的方式,一個(gè)應(yīng)用程序可能會(huì)執(zhí)行未對(duì)齊的內(nèi)存訪問,并提供一些建議的解決方案,以避免這些問題, 。
癥狀
上述問題,適用于所有ARM架構(gòu)。然而,根據(jù)MMU(內(nèi)存管理單元)和操作系統(tǒng)支持的可用性,應(yīng)用程序可能會(huì)看到不同的行為在不同的平臺(tái)上。默認(rèn)情況下,未對(duì)齊的內(nèi)存訪問不會(huì)被困住了,會(huì)導(dǎo)致不正確的數(shù)據(jù)。與功能的MMU的平臺(tái)上,但是,OS捕獲非對(duì)齊訪問,它在運(yùn)行時(shí)進(jìn)行糾正。其結(jié)果將是正確的數(shù)據(jù),但在10-20 CPU周期的成本。
常見原因
上述問題的類型轉(zhuǎn)換適用于所有ARM架構(gòu)。然而,根據(jù)MMU(內(nèi)存管理單元)和操作系統(tǒng)支持的可用性,應(yīng)用程序可能會(huì)看到不同的行為在不同的平臺(tái)上。默認(rèn)情況下,未對(duì)齊的內(nèi)存訪問不會(huì)被困住了,會(huì)導(dǎo)致不正確的數(shù)據(jù)。與功能的MMU的平臺(tái)上,但是,OS捕獲非對(duì)齊訪問,它在運(yùn)行時(shí)進(jìn)行糾正。其結(jié)果將是正確的數(shù)據(jù),但在10-20 CPU周期的成本。
代碼:
void my_func(char *a) { int *b = (int *)a; DBGPRINTF("%d", *b); }
這個(gè)簡(jiǎn)單的例子,可能會(huì)導(dǎo)致未對(duì)齊的內(nèi)存訪問,因?yàn)槲覀儾荒鼙WC的char * a是一個(gè)4字節(jié)的邊界上對(duì)齊。只要有可能,應(yīng)避免這種類型的施放。
使用數(shù)據(jù)緩沖區(qū)
未對(duì)齊的內(nèi)存訪問的最常見的原因源于不正確地處理數(shù)據(jù)緩沖區(qū)。這些數(shù)據(jù)緩沖區(qū)可能包含任何數(shù)據(jù)從USB端口讀取,通過網(wǎng)絡(luò),或從一個(gè)文件中。這個(gè)數(shù)據(jù)是很常見的包裝,有沒有插入填充,以確保數(shù)據(jù)在緩沖區(qū)內(nèi)位于其自然大小邊界。在這個(gè)例子中,我們會(huì)考慮的情況下,從文件加載的Windows BMP和解析的頭。的Windows BMP文件包含一個(gè)頭的像素?cái)?shù)據(jù)。的標(biāo)頭是由兩個(gè)結(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;
請(qǐng)注意,在的HEADER和INFOHEADER結(jié)構(gòu)的大小,分別為14和40字節(jié)。讓我們假設(shè)我們要確定在運(yùn)行時(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)); } } }
注意的寬度和高度的偏移量。因?yàn)樗麄儗儆谝粋€(gè)半字邊界上,以上述方式訪問這些值會(huì)導(dǎo)致未對(duì)齊的內(nèi)存訪問。下面列出的一些推薦的方法來避免這個(gè)問題。
推薦的解決方案
使用memcpy
我們的第一個(gè)選項(xiàng)是,只需執(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é)果是,存儲(chǔ)器被字節(jié)逐字節(jié),避免任何疑問對(duì)準(zhǔn)。
包裝的編譯器指令
或者,我們可以使用壓縮的編譯器指令允許使用指針,直接將我們需要的數(shù)據(jù),同時(shí)迫使編譯器來處理對(duì)齊問題。在BREW環(huán)境中,PACKED被定義如下:
代碼:
#ifdef __ARMCC_VERSION #define PACKED __packed #else #define PACKED #endif
包裝形式,通過指定一個(gè)指針,ARM編譯器將生成相應(yīng)的說明來正確地訪問內(nèi)存,無論對(duì)齊。修改后的版本,上面的例子中,使用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; } } }
雖然程序員通常會(huì)無法控制標(biāo)準(zhǔn)化的數(shù)據(jù)格式,如BMP頭在上面的例子中,當(dāng)你定義自己的數(shù)據(jù)結(jié)構(gòu)應(yīng)確保奠定了良好的對(duì)齊方式中的數(shù)據(jù)定義對(duì)齊的數(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ǎn)單地重新排列中,我們聲明的結(jié)構(gòu)成員,我們可以解決一些對(duì)齊的問題。另外請(qǐng)注意,如果未聲明為包裝,BAD_STRUCT,編譯器通常會(huì)插入填充,每個(gè)字段對(duì)齊。然而,這通常是不希望的,因?yàn)樗速M(fèi)內(nèi)存和避免幾乎總是可以簡(jiǎn)單地通過聲明為了減小尺寸的字段。
BREW模擬器測(cè)試
BREW模擬器3.1.2及以上版本提供了能夠使數(shù)據(jù)對(duì)齊檢查。BREW模擬器啟用此功能時(shí),將顯示一個(gè)對(duì)話框,通知您的每一個(gè)未對(duì)齊的內(nèi)存訪問,并為您提供的選項(xiàng)對(duì)這一問題視而不見,或闖入的代碼,請(qǐng)參閱BREW SDK用戶文檔一節(jié)揗isaligned數(shù)據(jù)異常支持更多信息,此功能。注:由于x86架構(gòu)的訪問未對(duì)齊的數(shù)據(jù)不會(huì)有任何問題,你可以不編譯模擬器的DLL使用__packed指令(PACKED這就是為什么在WIN32環(huán)境下的空白被定義為)。這意味著,通過使用PACKED指針的非對(duì)齊訪問,解決依舊會(huì)觸發(fā)在模擬器的對(duì)齊檢查。
評(píng)論