本文主要介紹了嵌入式單片機(jī)編程中,為什么大多時(shí)候要保證堆棧8字節(jié)對(duì)齊的問(wèn)題。
字節(jié)對(duì)齊原則
1. 結(jié)構(gòu)(struct)(或聯(lián)合(union)) 中的第一個(gè)數(shù)據(jù)成員放在 offset 為 0 的地方,以后每個(gè)數(shù)據(jù)成員存儲(chǔ)的起始位置,要從該成員大小或者成員的子成員大?。ㄖ灰摮蓡T有子成員,比如說(shuō)是數(shù)組,結(jié)構(gòu)體等)的整數(shù)倍開(kāi)始(比如 int 型變量在 32 位編譯環(huán)境下為 4 字節(jié),則要從 4 的整數(shù)倍地址開(kāi)始存儲(chǔ));
2. 如果一個(gè)結(jié)構(gòu)里有某些結(jié)構(gòu)體成員,則結(jié)構(gòu)體成員要從其內(nèi)部最大元素大小的整數(shù)倍地址開(kāi)始存儲(chǔ).
如:struct a 里存有 struct b, b 里有 char, int , double 等元素,那 b 應(yīng)該從 8 的整數(shù)倍開(kāi)始存儲(chǔ).;
3. 結(jié)構(gòu)體的總大小,也就是 sizeof 的結(jié)果,必須是其內(nèi)部最大成員的整數(shù)倍,不足的要補(bǔ)齊;
總大小為最大成員變量大小的整數(shù)倍,sizeof(AA) = 24;sizeof(BB) = 32。
#pragma pack():
在代碼前加一句 #pragma pack(1),會(huì)發(fā)現(xiàn) sizeof(AA) = 17;sizeof(BB) = 24;
AA 是 8+4+1+4=17;
BB 是 5+4+8+4+2+1=24;
這就是理想中的沒(méi)有內(nèi)存對(duì)齊的情況,所以 #pragma pack(1) 是告訴編譯器,所有的對(duì)齊都按照1的整數(shù)倍對(duì)齊,換句話說(shuō)就是沒(méi)有對(duì)齊規(guī)則。
即#pragma pack(n)就是所有的對(duì)齊都按照n的整數(shù)倍對(duì)齊。
Vc,Vs等編譯器默認(rèn)是 #pragma pack(8),所以測(cè)試我們的規(guī)則會(huì)正常;
gcc 默認(rèn)是 #pragma pack(4),并且 gcc 只支持 1, 2, 4 對(duì)齊。套用三原則里計(jì)算的對(duì)齊值是不能大于 #pragma pack 指定的n值。
為什么要保證堆棧8字節(jié)對(duì)齊
AAPCS 規(guī)則要求堆棧保持 8 字節(jié)對(duì)齊。如果不對(duì)齊,調(diào)用一般的函數(shù)也是沒(méi)問(wèn)題的。但是當(dāng)調(diào)用需要嚴(yán)格遵守 AAPCS 規(guī)則的函數(shù)時(shí)可能會(huì)出錯(cuò)。
例如調(diào)用 sprintf 輸出一個(gè)浮點(diǎn)數(shù)時(shí),棧必須是 8 字節(jié)對(duì)齊的,否則結(jié)果可能會(huì)出錯(cuò)。
實(shí)驗(yàn)驗(yàn)證:
1.在 A 處設(shè)置斷點(diǎn),讓程序全速運(yùn)行至 A
2.在 MDK 中修改 MSP 的值使 MSP 滿足 8 字節(jié)對(duì)齊
3.全速運(yùn)行程序,觀察 buf 中的字符為 1.234 結(jié)果正確
4.回到第 2 步,修改 MSP 使之只滿足 4 字節(jié)對(duì)齊而不滿足 8 字節(jié)對(duì)齊
5.全速運(yùn)行程序,觀察 buf 中的字符為 -2.000 結(jié)果錯(cuò)誤
該實(shí)驗(yàn)證明了調(diào)用 sprintf 輸出一個(gè)浮點(diǎn)數(shù)必須要保證棧 8 字節(jié)對(duì)齊。
編譯器為我們做了什么
先看一個(gè)實(shí)驗(yàn):
保證初始的時(shí)候堆棧是 8 字節(jié)對(duì)齊的;
1.在 A 處設(shè)置斷點(diǎn);
2.全速運(yùn)行至 A,觀察 MSP=0x2000025c,沒(méi)有 8 字節(jié)對(duì)齊;
3.略微修改一下 main 函數(shù)代碼如下,其他部分代碼不變;
4.同樣在 A 處設(shè)置斷點(diǎn);
5.全速運(yùn)行至 A,觀察 MSP=0x200002d8,這次 8 字節(jié)對(duì)齊了; 這個(gè)實(shí)驗(yàn)說(shuō)明了如果編譯器發(fā)現(xiàn)了某個(gè)函數(shù)需要調(diào)用浮點(diǎn)庫(kù)時(shí)會(huì)自動(dòng)調(diào)整編譯生成的匯編代碼,從而保證調(diào)用這些浮點(diǎn)庫(kù)函數(shù)時(shí)堆棧是8字節(jié)對(duì)齊的。
換句話說(shuō)如果我們保證了棧初始的時(shí)候是8字節(jié)對(duì)齊的,那么編譯器可以保證以后調(diào)用浮點(diǎn)庫(kù)時(shí)堆棧仍是8字節(jié)對(duì)齊的。
os下應(yīng)該怎樣設(shè)置任務(wù)堆棧
由上面的討論可知給任務(wù)分配棧時(shí)需要保證棧是 8 字節(jié)對(duì)齊的,不然在該任務(wù)中凡是調(diào)用 sprintf 的函數(shù)均會(huì)出錯(cuò),因?yàn)闂R婚_(kāi)始就是不對(duì)齊的。
是否保證了棧初始是8字節(jié)對(duì)齊了就萬(wàn)事大吉了呢。no!大家請(qǐng)看一種特殊的情況:
mian函數(shù)的反匯編如下:
保證初始的時(shí)候堆棧是 8 字節(jié)對(duì)齊的;
1.在 A 處設(shè)置斷點(diǎn);
2.全速運(yùn)行至 A,觀察此時(shí) MSP=0x200002e4 未對(duì)齊;
3.在 MDK 中將 SVC 的掛起位置 1;
4.在 B 處設(shè)置斷點(diǎn);
5.全速運(yùn)行至 B,觀察此時(shí) MSP=0x200002b4 未對(duì)齊;
6.繼續(xù)全速執(zhí)行,觀察 buf 中的字符為: -2.000 出錯(cuò)了;
這個(gè)實(shí)驗(yàn)說(shuō)明了即使保證棧初始是 8 字節(jié)對(duì)齊的,編譯器也只能保證在調(diào)用 sprintf 那個(gè)時(shí)刻棧是 8 字節(jié)對(duì)齊的,但不能保證任意時(shí)刻棧都是 8 字節(jié)對(duì)齊的,如果恰巧在 MSP 沒(méi)有 8 字節(jié)對(duì)齊的時(shí)刻發(fā)生了中斷,而中斷中又調(diào)用了 sprintf,這種情況下仍會(huì)出錯(cuò)。
Cortex-M3 內(nèi)核為我們做了什么
Cortex-M3 內(nèi)核提供了一種硬件機(jī)制來(lái)解決上述這種中斷中棧不對(duì)齊問(wèn)題。
CM3 中可以把 NVIC 配置控制寄存器的 STKALIGN 置位,來(lái)保證中斷中的棧 8 字節(jié)對(duì)齊。
具體實(shí)現(xiàn)過(guò)程如下:當(dāng)發(fā)生中斷時(shí)由硬件自動(dòng)檢測(cè) MSP 是否 8 字節(jié)對(duì)齊,如果對(duì)齊了,則不進(jìn)行任何操作,如果沒(méi)有對(duì)齊,則自動(dòng)將 MSP 減 4 這樣便對(duì)齊了,同時(shí)將 xPSR 的第 9 位置位來(lái)記錄這個(gè) MSP 的非正常的變化,在中斷返回若發(fā)現(xiàn) xPSR 的第 9 位是置位的則自動(dòng)將 MSP 加 4 調(diào)整回原來(lái)的值。
實(shí)驗(yàn)驗(yàn)證:
mian函數(shù)的反匯編如下:
1.在 A 處設(shè)置斷點(diǎn);
2.全速運(yùn)行至 A,觀察此時(shí) MSP=0x200002e4 未對(duì)齊;
3.在 MDK 中將 SVC 的掛起位置 1,同時(shí)將 0xE000ED14 處的值由 0x00000000 改為 0x00000200(即將 NVIC 配置控制寄存器的 STKALIGN 置位)
4.在 B 處設(shè)置斷點(diǎn);
5.全速運(yùn)行至 B,觀察此時(shí) MSP=0x200002b0 對(duì)齊了;
6.觀察中斷返回時(shí)的 MSP=0x200002e4 調(diào)整回來(lái)了;
7.繼續(xù)全速執(zhí)行,觀察 buf 中的字符為:1.234 正確;
這個(gè)實(shí)驗(yàn)說(shuō)明了將NVIC配置控制寄存器的STKALIGN置位可以保護(hù)中斷時(shí)棧仍是8字節(jié)對(duì)齊
總結(jié)
綜上所述,為了能夠安全的使用嚴(yán)格遵守AAPCS規(guī)則的函數(shù)(比如sprintf)需要做到以下幾點(diǎn):
保證MSP在初始的時(shí)候是8字節(jié)對(duì)齊的
如果用到OS的話,需要保證給每個(gè)任務(wù)分配的棧是保持8字節(jié)對(duì)齊的
如果用的是基于CM3內(nèi)核的處理器,需將NVIC配置控制寄存器的STKALIGN置位。
審核編輯:劉清
-
處理器
+關(guān)注
關(guān)注
68文章
19165瀏覽量
229146 -
寄存器
+關(guān)注
關(guān)注
31文章
5317瀏覽量
120013 -
存儲(chǔ)器
+關(guān)注
關(guān)注
38文章
7452瀏覽量
163608 -
SVC控制器
+關(guān)注
關(guān)注
0文章
2瀏覽量
5224 -
嵌入式單片機(jī)
+關(guān)注
關(guān)注
0文章
10瀏覽量
2260
原文標(biāo)題:對(duì)堆棧 8 字節(jié)對(duì)齊問(wèn)題的討論
文章出處:【微信號(hào):c-stm32,微信公眾號(hào):STM32嵌入式開(kāi)發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論