分享网址
http://wenku.baidu.com/link?url=e-xWNn7f84rrEf_vhHz5CQh2LCVmaGBTA6iB0BC8zPv_8eXz5SKmRofsSuenh8wn_JjeQZBD103xCA6wqDkYo9SzlIvKgCpYwwrbyNvbxVS
在一般的
C
教科书中,可以见到
6
种类型修饰符,分别是
: auto, const, register,
static, volatile, extern.
局部变量除非显式指明为
static,
否则默认为
auto
,所以一般不会在代码中使用类型
修饰符
auto.
在后编译器时代,优化器可以合理的分配寄存器,所以一般不会在代码中使用类型修
饰符
register.
extern
只用于声明全局变量,用法单一。
本节将主要介绍
const, static
和
volatile.
1. const
首先需要注意的是,
const
修饰的是在它前面的类型,如果它前面没有类型,那它修
饰的是紧跟着它的那个类型。
例如:
(a)const int i = 0;
和
(b)int const i = 0;
是完全一样的。
在
(a)
中,
const
前面没有类型,它就修饰它后面的那个
int
类型。在
(b)
中,
const
修饰它前
面的
int
类型,两者没有任何区别。
再看另一个稍复杂一点的例子,下面两条语句却不相同:
(c)const int *pi = 0;
/*
相当于
int const *pi = 0; pi
是一个指向
const int
的指针,复引用此运算符为得到一
个
const int
的类型,该类型不能作为左值,在该语句后使用类似于
*pi = 1
的操作将导致
编译错误。但该变量本身并不具备
const
属性,可以使用
pi = &i
的操作。可用于访问只读
存储器。
*/
(d)int* const pi = 0;
/* pi
是一个指向
int
类型的
const
指针,复引用此运算符为得到一个
int
类型,该类型可以
作为左值,在该语句可以使用类似于
*pi = 1
的操作,但该变量本身具备
const
属性,使用
pi = &i
的操作将导致编译错误。可用于访问固定位置的存储器。
*/
再看一个更复杂的例子:
(e)const int* const pi = 0;
/* pi
和
*pi
均不能作为左值。它只适合于读取某个固定位置的只读存储器
*/
const
还有下列典型用法
:
*
用于参数列表,通常修饰的是指针类型,表明该函数不会试图对传入的地址进行写
操作。例如:
void *memcpy(void *, const void *, size_t);
*
用于返回值,通常是一个指向只读区域的指针。例如:
const datatype_t *get_fixed_item(int index);
*
给固定不变的数据
(
例如码表
)
加上只读属性,在某些情况下可以减小
ram
的开销。
2.static
static
用于全局变量声明和局部变量声明具有完全不同的语义,不得不说,这是
C
语
言设计中的一个不合理之处。当
static
用于修饰全局变量声明
(
或函数声明,可以认为函数
声明就是声明一个指向代码段的指针,该指针的值最后由链接时决定,从这个意义上说,
函数声明也是一种全局变量声明
)
,它表示该变量具有文件作用域,只能被该源文件的代码
引用,不能被其他源文件中的代码访问。在编译时引起的实际变化是被
static
修饰的变量
不会被写入目标文件的输出节,在链接时解析其他模块中的未定义符号时不会被引用到。
它的反义词是
extern
。
例如:
------main.c---
extern int a(void);
int main(){ return a(); }
------a.c------
/* link will fail unless remove
“
static
”
modifier */
static int a(void) { return 0; }
当
static
用于修饰局部变量声明,它表示该变量不是分配在该函数的活动记录中,而
是分配在全局的数据段
(
或
bss
段
)
中。简单的说,就是被
static
修饰的局部变量实际上并不
是局部变量,而是具有函数作用域的全局变量,除了只能在定义它的函数内访问外
(
这是由
C
语法决定的
)
,它的运行时特征和全局变量完全一样,函数返回不会影响它的状态,它的
初始化仅有一次,发生在程序的装载时,而不是在每次函数调用的时候初始化。它的反义
词是
auto
。
例如
,
下面这段函数返回自己被调用了多少次:
int callee(void) {
static int times_called = 0;
return (++ times_called);
}
3.volatile
volatile
修饰符的作用是告诉优化器不能优化这个变量的读写操作,一定要为这个变
量的读写操作生成代码。
例如:
/*
延时操作
*/
int foo(void) {
/* 100
次减法后返回
*/
volatile int i = 100; /*(a)*/
while (i > 0) i--; /*(b)*/
return 0;
}
在无
volatile
修饰的情况下,因为变量
i
的变化对上下文无影响,所以优化器很可能
会省略掉对
i
操作的代码,而只生成
return 0
的代码,加上
volatile
可以保证编译器一定为
语句
(a)
和
(b)
生成代码,达到延时的目的。
/*
设备状态判定
*/
int uart_write_char(int c) {
/*
向串口发送寄存器写入待发送字符
*/
*(volatile unsigned int *)UART_TX_REG = c;
/*
判断是否已发送
*/
while ( (*(volatile unsigned int *)UART_STATUS_REG & TX_BIT) != 0); /*(c)*/
return 0;
}
在语句
(c)
中,如果不使用
volatile
,优化器可能会因为在两次读取
UART_STATUS_RE
G
之间没有对
UART_STATUS_REG
的写操作而将读取操作外提到循环体外而导致死循环。