1.static关键字
- static关键字有两层含义:
- 1. 修饰的变量的在程序运行时内存地址不变
当static修饰变量时,该变量被编译器分配在内存的全局区,程序运行时,该变量的生命周期伴随整个程序。在裸机开发中,可以认为静态变量的物理地址是静态的,即编译器编译之后,该变量的内存地址就确定了。(带操作系统时静态变量的地址是如何得我并不清楚) - 2. 修饰的对象被限制在某个范围内使用
static用于限定对象的作用范围时,修饰对象可以是变量和函数。
修饰函数->该函数被限定在该文件内调用,不可以在其他文件中使用extern关键字声明静态函数。
修饰变量->修饰变量分为两种,修饰全局变量和修饰局部变量。- 修饰全局变量: 全局变量本身就具有上方 ‘1…内存常驻’ 所述的属性,因此当static关键字修饰全局变量时,为其增加了一个限定范围属性,即该变量只能在该文件内调用,不可以在其他文件extern。
- 修饰局部变量: 局部变量使用范围被限定在子函数中,本身不具有内存常驻属性。其随着子函数的调用由CPU分配内存(得到内存),随着子函数的退出被收回内存(失去内存),得到与失去都是在栈中进行的。而static关键字的作用在于告诉编译器这个变量不要放在栈中,要给我单独在全局区分配内存给它。所以static为局部变量增加了一个常驻内存属性。
- 1. 修饰的变量的在程序运行时内存地址不变
使用对象 | 全局变量 | 局部变量 | 函数 |
---|---|---|---|
结果 | 缩减了作用域(本文件) | 延长了生命周期(常驻内存) | 缩减了作用域(本文件) |
2.const关键字
-
1.const用于全局变量
-
全局变量不加
const
- 未初始化: 分配到
.bss
区。 - 初始化: 分配到具有rw权限的
.data
区
- 未初始化: 分配到
-
全局变量加上
const
- 未初始化: 分配到
.bss
区,这时使用指针访问时可以成功的,但编译会有警告! - 初始化: 它会被分配到有rx权限的
.text
区, 使用指针访问会出现段错误
- 未初始化: 分配到
const修饰它本身之后的变量,如:
const int a = 10; int const a = 10;
均为定义了一个int型的常变量,并赋值10.
但这一点在与指针交织时有点乱,如:const int* p = address; //1 int const *p = address; //2 int* const p = address; //3
其中1与2是等价的,可见const之后的变量为*p,因为 *p为取值,也就是说const修饰指针p指向的地址的值,这样p也被称为常量指针,即指向常量的指针,指针指向的地址内容不可变。
3则不同,const之后仅仅修饰p这个指针本身,所以p被称为指针常量。指针本身的指向地址不可变。 -
-
2.const修饰局部变量,函数指针形参
- 该修饰并不会影响该变量在内存中的位置,常常用于一些库的接口形参上如常用的
size_t strlen(const char *s);
函数,用于告知库的使用者,在该函数的调用过程中,不会使用指针去修改这段内存上的值。另外,限定在该子函数内,不能对const修饰的变量进行赋值,自加,自减等操作,如:void foo(const int a, const int* p1, int* const p2){ const int b = 10; b = 100; //错误,对const修饰的变量进行赋值 a++; //错误,对const修饰的变量进行自加 a = 1; //错误,对const修饰的变量进行赋值 *p1 = 10; //错误,对常量指针进行赋值 p1 = &b; //正确 *p2 = 10; //正确 p2 = &b; //错误,对指针常量进行赋值 }
- 该修饰并不会影响该变量在内存中的位置,常常用于一些库的接口形参上如常用的
-
3.const修饰函数返回值
- 直接看代码
const int foo() { return 1; } void main(void) { int a = foo(); //错误 const int a = foo(); //正确 }
- 直接看代码
3.volatile关键字
-
防止编译器对一些变量进行优化
CPU在对操作数进行操作符运算时,操作数的来源有三种:立即数,寄存器操作数,内存操作数.链接: link.- 立即操作数: 指令要操作的数据以常量的形式出现在指令中,称为立即数,它只能作为源操作数 。
- 寄存器操作数: 指令要操作的数据存放在CPU中的寄存器里,指令中给出寄存器名即可。
- 内存操作数: 指令要操作的数据存放在内存某些单元中,指令中给出内存单元物理地址(实际上指令只给出了偏移地址,段地址采用隐含方式给出,也可以使用跨段方式指出当前段地址)。
由于寄存器操作数的访问速度更快,编译器会尝试对其中的内存操作数进行优化,将其直接放在CPU的某个寄存器中,比如访问频率很高的循环变量’i’,比如如下代码:
int i = 0; int main(void) { while(i < 100) { printf("%d\r\n", i++); } return 0; }
由于编译器在检索完程序后,会尝试将i优化为寄存器变量,变为寄存器操作数,仅在首次使用时从内存读取到CPU寄存器。如果发生了,此后while每循环一次,CPU寄存器中的i会加1,而此时内存中的i并不会被CPU重复读取。由于CPU读取自身寄存器的速度快于读取内存的速度,因此这会提高程序的运行效率,同时这个有可能引来灾难,如果外界在CPU不知情的情况下修改了内存中的i值,CPU仍自顾自的以寄存器中的i值进行运算,将会导致无法预料的后果。
可能的情况有:
- 全局i的值可能被其他的中断程序控制。
- 全局变量作为临界资源,被其他线程控制。
因此,在上述可能性存在时,应该给变量加上volatile修饰符,告知编译器,每次要读这个变量的值时,都必须去该变量所在的内存中去读取,而不是使用CPU寄存器中的备份。