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