Sprintf格式化float型引發的問題!

2022-05-04 16:09:07 字數 2965 閱讀 4223

專案需要列印一串浮點型數字,於是剛好用sprintf函式格式化,然後出現了意想不到的問題,float型數字全是0.00.。而後面的數字也出現錯誤。網上查詢原因,才發現沒有遵循aapcs棧使用規約,看了之後發現之前寫程式真是在冒險。

規約規定,棧任何時候都得4位元組對齊,在呼叫入口得8位元組對齊。在這個約定裡,棧的4位元組對齊確實得任何時候都遵守,而且你想不遵守都難,因為sp的最後兩位是硬體上保持0的。而對於8位元組對齊,這就需要碼農和編譯器配合著來。需要說明的一點是,8位元組對齊即使不遵守,一些情況下也沒問題,只要主調和被呼叫例程兩邊把堆疊使用,傳參,返回等處理好就行,也就是說兩邊有自己的一套約定就行。但是有時候,主調這邊在呼叫嚴格遵守aapcs的函式時,沒有將棧保持在8位元組對齊上,那就會出問題。

貼兩張圖可以看到位元組對齊後的效果,擷取自.map檔案

__align(8) char tcp_server_tramsvbuf[tcp_server_tx_bufsize];//8位元組對齊

__align(4) char tcp_server_tramsvbuf[tcp_server_tx_bufsize]; //4位元組對齊

char tcp_server_tramsvbuf[tcp_server_tx_bufsize];
對於乙個char型的陣列,棧頂在任意位置都可以,但是當它涉及到sprintf輸出乙個浮點數時,就可能出錯。aapcs規則要求堆疊保持8位元組對齊。如果不對齊,呼叫一般的函式也是沒問題的。但是當呼叫需要嚴格遵守aapcs規則的函式時可能會出錯。

例如呼叫sprintf輸出乙個浮點數時,棧必須是8位元組對齊的,否則結果可能會出錯。

什麼是棧對齊?棧的位元組對齊,實際是指棧頂指標須是某位元組的整數倍。

位元組對齊跟資料在記憶體中的位置有關,如果乙個變數的記憶體位址正好位於它長度的整數倍,他就被稱做自然對齊。比如在32位cpu下,假設乙個整型變數的位址為0x00000004,那它就是自然對齊的。

程式設計時,對於aapcs棧使用約定的遵守,總的來說就兩條:

1. 彙編檔案中需要我們親自動手來保證遵守aapcs棧使用約定。

(特別注意每次從彙編進入c的世界時,要保證彙編部分的編碼在呼叫c介面時棧是8位元組對齊的,不要疏忽了,因為c編譯器可不負責調整。c編譯器說你得送給我的sp就是8位元組對齊的,我才能保證接下來的c部分沒有結束之前,遵守aapcs棧使用約定)

2. 在c檔案中,由編譯器來處理。

cortex-m3 中斷控制器的棧對齊調整功能(該功能在r2p0版本以後的核心中均預設開啟,stkalign位預設為1)

cortex m3 nvic ccr暫存器(控制與配置暫存器)的stkalign位置1,那麼在發生中斷時,進入中斷響應函式前,核心會首先檢查當前正在使用的棧指標是否8位元組對齊,如果是,則正常將xpsr,pc,lr,sp,r0-r3入棧,如果不是,則先把sp-4,調整為8位元組對齊,然後將xpsr第九位置1,接著把xpsr,pc,lr,sp,r0-r3入棧,再然後才進入中斷響應函式。這樣可以保證程式在執行過程中,如果在棧沒有發生4位元組對齊的地方發生中斷了,進入到中斷響應函式的時候也是遵守aapcs棧使用約定的。如果中斷服務程式是做任務切換的,那麼前面的情況就是將任務棧調整為對齊,然後進入異常服務程式後使用系統棧,那如果系統棧本來就是不對齊的呢?通過中斷來做任務切換的情況下,中斷控制器並不會對系統棧進行調整,怎麼辦?其實這也不用擔心,以μc/os-ii為例,在cortex-m3上通常使用pendsv異常來做任務切換,即將osctxsw以及osintctxsw都設為僅完成pendsv異常觸發功能,然後在pendsv異常服務程式中進行任務切換。由於上電時刻系統處於特權級模式,只要我們保證從上電開始到第一次系統呼叫,使用的棧都是系統棧msp就可以了,這樣即使第一次要進入任務切換時msp不對齊,中斷向量控制器也會給調整為8位元組對齊狀態,雖然這個第一次任務切換後除了中斷再也不會使用msp,但只要我們同時保證所有彙編部分都不會破壞8位元組對齊規約,那麼從此以後msp都會是8位元組對齊的。

__align(num) 和__packed

__align(num)這個用於修改最高端別物件的位元組邊界。在彙編中使用ldrd或者strd時  就要用到此命令__align(8)進行修飾限制,來保證資料物件是相應對齊。這個修飾物件的命令最大是8個位元組限制,可以讓2位元組的物件進行4位元組   對齊,但是不能讓4位元組的物件2位元組對齊。  __align是儲存類修改,他只修飾最高端型別物件,不能用於結構或者函式物件。

__packed是進行一位元組對齊,用於c語言結構的壓縮,沒有填充和對齊。  

1.不能對packed的物件進行對齊  

2.所有物件的讀寫訪問都進行非對齊訪問  

3.float及包含float的結構聯合及未用__packed的物件將不能位元組對齊  

4.__packed對區域性整形變數無影響  

5.強制由unpacked物件向packed物件轉化是未定義,整形指標可以合法定  

義為packed。  

在 cotex-m3 programming manual 中有提到對齊問題

1.通常編譯器在生成**的時候都會進行結構體填充,保證(結構體內部成員)最高效能的對齊方式。

2.編譯器自動分配出來結構體的記憶體(比如定義為全域性變數或區域性變數)肯定是對齊的。

3.查閱幫助文件的malloc部分,mdk的標準malloc申請的記憶體區時8位元組對齊的。

4.若自定義的malloc函式本身沒有對分配的記憶體實現4位元組或以上的對齊操作,分配出來的不對齊的記憶體,編譯器是不知道的,所以很可能會產生問題。

此時最好的解決方式在記憶體池陣列前新增__align(4)關鍵字,只需保證自定義malloc分配出來的首位址是4位元組對齊。

比如:__align(4) u8 mem1base[mem1_max_size];

參考:

sprintf格式化輸出

函式原型 int sprintf char buffer,const char format argument 返回值 字串長度 strlen 引數說明及應用舉例 sprintf格式的規格如下所示。中的部分是可選的。指定引數 識別符號 寬度 精度 指示符 若想輸出 本身時,請這樣 處理。1.處理字元...

sprintf 格式化輸出函式

功能 函式sprintf 用來作格式化的輸出。用法 此函式呼叫方式為int sprintf char string,char format,arg list 說明 函式sprintf 的用法和printf 函式一樣,只是sprintf 函式給出第乙個引數string 一般為字元陣列 然後再呼叫out...

sprintf 格式化輸出函式

sprintf 格式化輸出函式 圖形 功能 函式sprintf 用來作格式化的輸出。用法 此函式呼叫方式為int sprintf char string,char format,arg list 說明 函式sprintf 的用法和printf 函式一樣,只是sprintf 函式給出第乙個引數stri...