ARM平臺的字節對齊問題

 前言
ARM流行已久,做嵌入式開發的不知道ARM不大可能。鑑於其所具備的較低功耗下的較高性能,也就成了大多數嵌入式設備的首選
不過對於剛上手的人來說,有可能會遇到一些稀奇古怪的問題。畢竟大部分人都習慣了IA-32下的程序設計,雖然兩者都是32位的
構完全不同,於是也導致了一些隱含的問題。這裏想描述一下一個有點蠱惑的問題,即在ARM上訪問非對齊地址內容,會出現所
問題。
ARM內存訪問的對齊問題
按照ARM文檔上的描述,其訪問規則如下:
1. 一次訪問4字節內容,該內容的起始地址必須是4字節對齊的位置上;
2. 一次訪問2字節內容,該內容的起始地址必須是2字節對齊的位置上;
(單字節的沒有這個問題,就不用考慮啦。 )
好,既然規則如此,那應該遵守。不過麼,不安分的人往往喜歡破壞規則,喜歡看看不遵守規則會有什麼結果;另外麼,即便遵
免考慮不周,犯個錯也是正常現象。好,那麼讓我們來看看犯錯的結果吧。例如下面的代碼:
char buff[8] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xab, 0xbc, 0xcd};
int v32, *p32;
short v16, *p16;
p32 = (int*)&( buff[1] ); //unalignment
p16 = (short*)&( buff[1] ); //unalignment
v32 = *p32; //what’s the result?
v16 = *p16; //what’s the result?
如果上面這段代碼在IA-32上運行,那麼結果應該如下:
v32 = 0x9a785634
v16 = 0x5634
即便非對齊地址上訪問,IA-32也就是犧牲一點性能,但是結果保證是正確的。恩,這也是我們所期望的……
可是…… 換到ARM上呢?我們來看看在ADS1.2編譯後,執行的結果如下:

v32 = 0x12785634
v16 = 0x1234
這個結果有點奇怪了吧。照理說指向0x34,那麼如果是Big-Endian的話,v32應該是0x3456789a,如果是Little-Endian的話,就是前面
可現在的結果呢?兩者都不是,莫名地把更低地址的0x12給湊進來了…… 而如果看看編譯生成的彙編code的話,這兩個賦值很
ldrsh指令,指令沒有問題,分別用於讀取32位和16位數據,都是最基本的指令。嗯,嗯,這就是我們所要描述的訪問非對齊地址的
問題的緣由(個人猜測,非官方資料……)
個人感覺呢,這是ARM體系架構實現的問題,或者說這本來就是By Design的。這樣做簡化了處理器的實現,IA-32實現的時候肯定
齊進行判斷,然後轉換爲相應的操作 ,而ARM呢?沒有做這個事情,默認認爲大家都按照規矩辦事,你要是膽敢破壞,俺就給你
那有沒有辦法解決呢?
這個問題其實ARM自己也知道,所以呢,它在編譯器裏面,已經添加了部分支持。不過有人會問,那上面那個情況呢?爲什麼結
有添加什麼支持嘛……
嗯,其實ARM是做了一定的努力的,只是這個情況它沒辦法解決…… 它做的事情就是:在編譯器能夠的得知的情況下,儘量保證訪問
話有點籠統,那麼把具體情況一個個來看看吧。
編譯器的努力(1)—— 所有局部/全局/靜態等變量都放在4字節對齊的地址上
其實這個努力很常見,由於在32位平臺上,一次訪問4字節是效率最高的,所以大多數32平臺的編譯器都如此處理,ARM的ADS也不例外
編譯器的努力(2)—— 填充、填充、再填充
這個事情麼,其實也是常見的。各類編譯器上,對於某些結構定義中會產生不對齊的情況,自動填充,以提高訪問效率(例如IA
會加1個週期的)。而ARM的編譯器也一樣操作,不過感覺這裏不單單是爲了提高效率,也能夠順帶解決這個不對齊的問題。
編譯器的努力(3)—— 產生特殊代碼
嗯,這個就是關鍵了,也是ARM編譯器的與衆不同之處。先來看一段代碼:
__packed typedef struct _test
{
char a;
short c;
int d;
} test;
char buff[8] = {0x12, 0x34, 0x56, 0x78, 0x9a, 0xab, 0xbc, 0xcd};
test *p = (test *)buff;
v32 = p->d; //這裏的v32借用上面的定義;
貌似多了個限定爲__packed的struct,以此來造成不對齊的狀況,看不出多大區別嘛。可是運行一下的話,就會發現這裏的結果是正
ADS生成的彙編代碼吧。
v32 = q->d;
[0xe2890003] add r0,r9,#3
[0xeb000088] bl __rt_uread4
[0xe1a05000] mov r5,r0

看到這裏的那條"bl __rt_uread4"的指令了吧。對ARM指令有一定了解的都知道bl其實就是一個函數調用。所以,這裏的代
自己提供的__rt_uread4函數,該函數完成的操作就是讀取四個字節。ADS提供了類似的一系列函數,針對signed/unsigned,以及
寫入操作。
估計看到這裏,大家會問,如果沒有__packed限定符呢?猜對了,沒有__packed限定符,那麼編譯器會對上面的情況pending,
所在的位置是4字節對齊的(編譯期信息,而非實際運行期信息)。所以就回到類似最初的例子了。
那麼,還有一種情況,就是在有__packed的情況下,而struct裏的字段都是符合對齊要求的,那麼生成的代碼會是怎麼樣的呢?
看,和上面的這段彙編代碼,唯一的區別就是第一條指令把#3改成了#4,而後面仍舊調用__rt_uread4函數。嗯,這樣結論就出
編譯器會在使用__packed的情況下,自動對其中的4字節/2字節訪問添加特殊代碼,以保證其結果的正確。
好了,這個關於這個問題描述得差不多了,可能的話,儘量倚賴編譯器的這些功能,而對於編譯器無能爲力的部分,就要靠萬分小心了
p.s. 其實這裏有很多事情可以來儘量預防此類問題,比如嵌入式項目往往喜歡自己管理內存分配,那麼自己寫的內存分配函數
字節對齊位置上的……

轉自:http://www.ddsic.com/modules/newbb/report.php?forum=19&topic_id=84&view

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章