今天調試程序時,想觀察一下變量的情況,突然發現平時經常移植別人程序時最容易忽略的一個致命問題,那就是忽略變量類型,這里有必要給大家一定知識啦,都是庫里面的,非原創!
3.0以后的版本中使用了CMSIS數據類型,變量的定義有所不同,但是出於兼容舊版本的目的,以上的數據類型仍然兼容。CMSIS的IO類型限定詞如表 5‑7所示,CMSIS和STM32固件庫的數據類型對比如表 5‑8所示。這些數據類型可以在STM32F10x_StdPeriph_Lib_V3.4.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\stm32f10x.h中找到具體的定義,此部分定義如下。
1 /*!< STM32F10x Standard PeripheralLibrary old types (maintained for legacy purpose) */
2
3typedef int32_t s32;
4
5typedef int16_t s16;
6
8
9typedef const int32_t sc32; /*!< Read Only */
10
11 typedef const int16_t sc16; /*!< ReadOnly */
12
13 typedef const int8_t sc8; /*!< ReadOnly */
14
16
17 typedef __IO int16_t vs16;
18
19 typedef __IO int8_t vs8;
20
21 typedef __I int32_t vsc32; /*!< ReadOnly */
22
23 typedef __I int16_t vsc16; /*!< ReadOnly */
24
25 typedef __I int8_t vsc8; /*!< ReadOnly */
26
27 typedef uint32_t u32;
28
29 typedef uint16_t u16;
30
31 typedef uint8_t u8;
32
33 typedef const uint32_t uc32; /*!<Read Only */
34
35 typedef const uint16_t uc16; /*!< ReadOnly */
36
37 typedef const uint8_t uc8; /*!< ReadOnly */
38
39 typedef __IO uint32_t vu32;
40
41 typedef __IO uint16_t vu16;
42
43 typedef __IO uint8_t vu8;
44
45 typedef __I uint32_t vuc32; /*!< ReadOnly */
46
47 typedef __I uint16_t vuc16; /*!< ReadOnly */
48
49 typedef __I uint8_t vuc8; /*!< ReadOnly */
CMSIS IO類型限定詞
IO類限定詞 |
#define |
描述 |
_I |
volatile const |
只讀訪問 |
_O |
volatile |
只寫訪問 |
_IO |
volatile |
讀和寫訪問 |
固件庫與CMSIS數據類型對比
固件庫類型 |
CMSIS類型 |
描述 |
s32 |
int32_t |
易揮發只讀有符號32位數據 |
s16 |
int16_t |
易揮發只讀有符號16位數據 |
s8 |
int8_t |
易揮發只讀有符號8位數據 |
sc32 |
const int32_t |
只讀有符號32位數據 |
sc16 |
const int16_t |
只讀有符號16位數據 |
sc8 |
const int8_t |
只讀有符號8位數據 |
vs32 |
_IO int32_t |
易揮發讀寫訪問有符號32位數據 |
vs16 |
_IO int16_t |
易揮發讀寫訪問有符號16位數據 |
vs8 |
_IO int8_t |
易揮發讀寫訪問有符號8位數據 |
vsc32 |
_I int32_t |
易揮發只讀有符號32位數據 |
vsc16 |
_I int16_t |
易揮發只讀有符號16位數據 |
vsc8 |
_I int8_t |
易揮發只讀有符號8位數據 |
u32 |
uint32_t |
無符號32位數據 |
u16 |
uint16_t |
無符號16位數據 |
u8 |
uint8_t |
無符號8位數據 |
uc32 |
const uint32_t |
只讀無符號32位數據 |
uc16 |
const uint16_t |
只讀無符號16位數據 |
uc8 |
const uint8_t |
只讀無符號8位數據 |
vu32 |
_IO uint32_t |
易揮發讀寫訪問無符號32位數據 |
vu16 |
_IO uint16_t |
易揮發讀寫訪問無符號16位數據 |
vu8 |
_IO uint8_t |
易揮發讀寫訪問無符號8位數據 |
vuc32 |
_I uint32_t |
易揮發只讀無符號32位數據 |
vuc16 |
_I uint16_t |
易揮發只讀無符號16位數據 |
vuc8 |
_I uint8_t |
易揮發只讀無符號8位數據 |
stm32f10x.h文件中還包含了常用的布爾形變量定義,如:
1 typedef enum {RESET = 0, SET = !RESET} FlagStatus, ITStatus;
2
3 typedef enum {DISABLE = 0, ENABLE = !DISABLE} FunctionalState;
4
5 #define IS_FUNCTIONAL_STATE(STATE) (((STATE) == DISABLE) || ((STATE) ==ENABLE))
6
7 typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrorStatus;
不同版本的標准外設庫的變量定義略有不同,如3.4版本中就沒有之前版本的TRUE和FALSE的定義,用戶也可以根據自己的需求按照上面的格式定義自己的布爾形變量。在使用標准外設庫進行開發遇到相關的定義問題時應首先找到對應的頭文件定義。
字長和數據類型
1.字、雙字的含義
有些操作系統和處理器不把它們的標准字長稱作字,相反,出於歷史原因和某種主觀的命名習慣,它們用字來代表一些固定長度的數據類型。比如說,一些系統根據長度把數據划分為字節(byte,8位),字(word,16位),雙字(double words 32位)和四字(quad words 64位),而實際上該機是32位的。在本書中—在Linux中一般也是這樣—象我們前面所討論的那樣,一個字就是代表處理器的字長。
對於支持的每一種體系結構,Linux都要將<asm/types.h>中的BITS_PER_LONG定義為C long類型的長度,也就是系統的字長。表1是Linux支持的體系結構和它們的字長的對照表。
表1 Linux支持的體系結構
體系結構 描 述 字 長 alpha Digital Alpha 64位 arm Arm and StrongArm 32位 cris CRIS 32位 h8300 H8/300 32位 i386 Intel x86 32位 ia64 IA-64 64位 m32r M32xxx 32位 m68k motorola 68k 32位 m68knommu M68k without MMU 32位 mips MIPS 32位 mips64 64位MIPS 64位 parisc HP PA_RISC 32位或64位 ppc PowerPC 32位 ppc64 POWER 64位 s390 IBM S/390 32位或64位 sh Hitachi SH 32位 sparc SPARC 32位 sparc64 UltraSPARC 64位 um Usermode Linux 32位或64位 v850 v850 32位 x86_64 x86-64 64位 |
情況其實還會更加復雜,因為用戶空間使用的數據類型和內核空間的數據類型不一定要相互關聯。sparc64體系結構就提供了32位的用戶空間,其中指針、int和long的長度都是32位。而在內核空間,它的int長度是32位,指針和long的長度卻是64。沒有什么標准來規范這些。
牢記下述准則:
* ANSI C標准規定,一個char的長度一定是8位。
*盡管沒有規定int類型的長度是32位,但在Linux當前所有支持的體系結構中,它都是32位的。
* short類型也類似,在當前所有支持的體系結構中,雖然沒有明文規定,但是它都是16位的(although no rule explicitly decrees that)。
*決不應該假定指針和long的長度,在Linux當前支持的體系結構中,它們就可以在32位和64位中變化。
*由於不同的體系結構long的長度不同,決不應該假設sizeof( int ) == sizeof( long )。
*類似地,也不要假設指針和int長度相等。
2. 不透明類型
不透明數據類型隱藏了它們內部格式或結構。在C語言中,它們就像黑盒一樣。支持它們的語言不是很多。作為替代,開發者們利用typedef聲明一個類型,把它叫做不透明類型,希望其他人別去把它重新轉化回對應的那個標准C類型。通常開發者們在定義一套特別的接口時才會用到它們。比如說用來保存進程標識符的pid_t類型。該類型的實際長度被隱藏起來了—盡管任何人都可以偷偷撩開它的面紗,發現它就是一個int。如果所有代碼都不顯式地利用它的長度(顯式利用長度這里指直接使用int類型的長度,比如說在編程時使用sizeof(int)而不是sizeof(pid_t)—譯者注),那么改變時就不會引起什么爭議,這種改變確實可能會出現:在老版本的Unix系統中,pid_t的定義是short類型。
另外一個不透明數據類型的例子是atomic_t,它放置的是一個可以進行原子操作的整型值。盡管這種類型就是一個int,但利用不透明類型可以幫助確保這些數據只在特殊的有關原子操作的函數中才會被使用。不透明類型還幫助我們隱藏了類型的長度,但是該類型也並不總是完整的32位,比如在32位SPARC體系下長度被限制。
內核還用到了其他一些不透明類型,包括dev_t、gid_t和uid_t等等。處理不透明類型時的原則是:
*不要假設該類型的長度。
*不要將該類型轉化回其對應的C標准類型使用。
*編程時要保證在該類型實際存儲空間和格式發生變化時代碼不受影響。
3. 指定數據類型
內核中還有一些數據雖然無需用不透明的類型表示,但它們被定義成了指定的數據類型。jiffy數目和在中斷控制時用到的flags參數就是兩個例子,它們都應該被存放在unsigned long類型中。
當存放和處理這些特別的數據時,一定要搞清楚它們對應的類型后再使用。把它們存放在其他如unsigned int等類型中是一種常見錯誤。在32位機上這沒什么問題,可是64位機上就會捅婁子了。
4. 長度明確的類型
作為一個程序員,你往往需要在程序中使用長度明確的數據。像操作硬件設備,進行網絡通信和操作二進制文件時,通常都必須滿足它們明確的內部要求。比如說,一塊聲卡可能用的是32位寄存器,一個網絡包有一個16位字段,一個可執行文件有8位的cookie。在這些情況下,數據對應的類型應該長度明確。
內核在<asm/typs.h>中定義了這些長度明確的類型,而該文件又被包含在文件<linux/types.h>中。表2有完整的清單。
表2 長度明確的數據類型
類型 描 述 s8 帶符號字節 u8 無符號字節 s16 帶符號16位整數 u16 無符號16位整數 s32 帶符號32位整數 u32 無符號32位整數 s64 帶符號64位整數 u64 無符號64位整數 |
其中帶符號的變量用的比較少。
這些長度明確的類型大部分都是通過typedef對標准的C類型進行映射得到的。在一個64位機上,它們看起來像:
typedef signed char s8;
typedef unsigned char u8;
typedef signed short s16;
typedef unsigned short u16;
typedef signed int s32;
typedef unsigned int u32;
typedef signed long s64;
typedef unsigned long u64;
而在32位機上,它們可能定義成:
typedef signed char s8;
typedef unsigned char u8;
typedef signed short s16;
typedef unsigned short u16;
typedef signed int s32;
typedef unsigned int u32;
typedef signed long long s64;
typedef unsigned long long u64;
上述的這些類型只能在內核內使用,不可以在用戶空間出現(比如,在頭文件中的某個用戶可見結構中出現)。這個限制是為了保護命名空間。不過內核對應這些不可見變量同時也定義了對應的用戶可見的變量類型,這些類型與上面類型所不同的是增加了兩個下畫線前綴。比如,無符號32位整形對應的用戶空間可見類型就是__u32。該類型除了名字有區別外,與u32相同。在內核中你可以任意使用這兩個名字,但是如果是用戶可見的類型,那你必須使用下畫線前綴的版本名,防止污染用戶空間的命名空間。
5. char型的符號問題
C標准表示char類型可以帶符號也可以不帶符號,由具體的編譯器、處理器或由它們兩者共同決定到底char是帶符號合適還是不帶符號合適。
大部分體系結構上,char默認是帶符號的,它可以自-128到127之間取值。而也有一些例外,比如ARM體系結構上,char就是不帶符號的,它的取值范圍是0~255
舉例來說,在默認char不帶符號,下面的代碼實際會把255而不是-1賦予i:
char i = -1;
而另一種機器上,默認char帶符號,就會確切地把-1賦予i。如果程序員本意是把-1保存在i中,那么前面的代碼就該修改成:
signed char i = -1;
另外,如果程序員確實希望存儲255,那么代碼應該如下:
unsigned char = 255;
如果你在自己的代碼中使用了char類型,那么你要保證在帶符號和不帶符號的情況下代碼都沒問題。如果你能明確要用的是哪一個,那么就直接聲明它。