前言:在寫程序時候遇到了一些關于數據類型轉換的問題,編譯器也沒有報錯,運行時才發現數據不對,找bug花費了很多時間,但最終也發現是一些細節上的問題,特地在這里整理出一篇文章記錄。
實驗環境:
芯片架構:Cortex-M0+
開發IDE:Keil_v5
編譯器:armcc
問題代碼:
uint64_t TempData2;
uint8_t KeyeBuf1[8];
TempData2 = ( KeyeBuf1[0] + (KeyeBuf1[1] << 8) + (KeyeBuf1[2] << 16) + (KeyeBuf1[3] << 24)+
(KeyeBuf1[4] << 32) + (KeyeBuf1[5] << 40) + (KeyeBuf1[6] << 48));
在上述代碼中,先前已經聲明了TempData2的數據類型為無符號64位整型類型,在接下來的移位后累加數據總是出現差錯,檢查累加后數據范圍也沒有超過無符號64位整型數據范圍。
問題分析:
經過一系列的排查,最終發現了問題所在,主要是以下三方面造成的影響:
1.機器平臺與編譯器的影響
stdint.h是C99中引進的一個標準C庫的文件,目前大部分單片機的C編譯器均支持。關于編譯器支的更多數據類型完全可以在該文件中找到。以armcc編譯器為例,文件中,對于8位,16位,32位,64位無符號整數類型的數據定義為如下:
#include
uint64_t TempData2;
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
typedef unsigned __INT64 uint64_t;
查找該文件可得uint64_t的類型是unsigned __INT64類型,__INT64類型的解釋與機器平臺和編譯器相關。
機器平臺的硬件架構以及編譯器的編譯最終都會影響執行代碼的生成。Cortex-M0+是32位的處理器,內部的寄存器大多都是32位,這就導致了在默認情況下進行的運算,不管是8位,16位,32位都在32位寄存器中運算。
2.“=”符號運算順序的影響
=符號的執行過程細分下來可以分成兩步:
計算=右邊部分得出最終結果
最后把結果賦給=左邊變量
由于上述的運算順序導致,=左邊變量不管原先定位為多少位,對先執行的=右邊計算過程沒有影響,也就是說,在上述代碼過程中,即使左邊變量先定義為64位,在先進行右邊部分計算時,還是按照右邊部分數據最大的數據類型來數據對齊,而后進行計算。
3.移位程序移植性的影響
對于移位程序來說,右移數據基本不會出太大問題,但是左移就需要注意很多。在32位機器平臺上,8位,16位,32位數據類型都會在32位寄存器中進行移位,只要我們確保移位后的數據不會超過32位數據類型,那么程序就會正常運行。而上述數據類型一旦移位后的數據類型超過32位,那么處理器會丟失左移向前進的數據,留下最低的32位。
問題解決:
對于上述代碼進行分析,( KeyeBuf1[0] + (KeyeBuf1[1] << 8) + (KeyeBuf1[2] << 16) + (KeyeBuf1[3] << 24)+ (KeyeBuf1[4] << 32) + (KeyeBuf1[5] << 40) + (KeyeBuf1[6] << 48));中的Keye1數組數據類型都是8位,計算時都在32位寄存器中計算。前幾個數據的移位沒有超過32位數據類型不會出太大錯誤,從(KeyeBuf1[4] << 32)起,理論上移位后的數據超過了32位,只留下最低32位,導致數據出錯。即使最后賦給了一個64位的變量,也是將一個32位數據賦給64位,而這些問題在編譯時期編譯器并不會指出,需要我們自己多加注意。
最后的解決方法是將KeyeBuf1數據在運算時強制轉換為64位數據類型,這樣進行運算時都是64位數據對齊,最后賦給一個64位數據類型,就不會出現數據丟失的情況了。代碼如下:
TempData2 = ( KeyeBuf1[0] + (KeyeBuf1[1] << 8) + (KeyeBuf1[2] << 16) + ((uint64_t)KeyeBuf1[3] << 24)+
((uint64_t)KeyeBuf1[4] << 32) + ((uint64_t)KeyeBuf1[5] << 40) + ((uint64_t)KeyeBuf1[6] << 48));