今天调试程序时,想观察一下变量的情况,突然发现平时经常移植别人程序时最容易忽略的一个致命问题,那就是忽略变量类型,这里有必要给大家一定知识啦,都是库里面的,非原创!
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类型,那么你要保证在带符号和不带符号的情况下代码都没问题。如果你能明确要用的是哪一个,那么就直接声明它。