1、const 解析
const 关键字有如下特点:
- const 修饰的变量本质上还是一个变量,只不过这个变量是只读的
- const 修饰的局部变量在栈上分配空间
- const 修饰的全局变量在全局数据区分配空间
- const 只在编译器有用,在运行期无用
注意:const 修饰的变量不是真正的常量,只是告诉编译器该变量不能出现在赋值符号的左边。const 变量只是说我不会修改这块内存,并不代表别人不修改。
1.1 const 全局变量的分歧
对于 const 修饰的局部变量在栈上分配内存,其值还是可以更改的。
对于 const 修饰的全局变量
- 标准 C 语言编译器不会将 const 修饰的全局变量存储在只读存储区,而是存储于可修改的全局数据区,其值依然可以改变;
- 现代 C 语言编译器中,修改 const 全局变量将导致程序崩溃,因为现代 C 编译器中的 const 将具有全局声明周期的变量存储于只读存储区。
程序实验:const 的变量本质
//9-1.c
#include<stdio.h>
const int g_c = 2;
int main(){
const int c = 1;
int* p = (int*)&c;
printf("c = %d\n", c);
*p = 3;
printf("c = %d\n", c);
p = (int*)&g_c;
printf("g_c = %d\n", g_c);
*p = 4;
printf("g_c = %d\n", g_c);
return 0;
}
对上面的结果:const 修饰的局部变量值可以修改, const 修饰的全局变量存储于只读存储区,修改会导致程序崩溃
1.2 const 的本质
- C 语言中的 const 使得变量具有只读属性
- 现代 C 编译器中的 const 将具有全局生命周期的变量存储于只读存储区
const 不能定义真正意义上的常量,只是表示我不去修改这个变量。
//9-2.c
#include<stdio.h>
const int g_array[5] = {0};
void modify(int* p, int v){
*p = v;
}
int main(){
int const i = 0;
const static int j = 0;
int const array[5] = {0};
modify((int*)&i, 1); // ok
// modify((int*)&j, 2); // error
modify((int*)&array[0], 3); // ok
// modify((int*)&g_array[0], 4); // error
printf("i = %d\n", i);
printf("j = %d\n", j);
printf("array[0] = %d\n", array[0]);
printf("g_array[0] = %d\n", g_array[0]);
return 0;
}
对上面的程序,全局变量和静态局部变量都具有全局生命周期,使用 const 修饰,变量存储于只读存储区,不可修改。i 和 array[5] 是局部变量,虽然具有只读属性,仅仅表示通过变量 i 和 array 不会修改内存,并不代表别人不去修改。
1.3 const 修饰的函数参数和返回值
- const 修饰的函数参数表示在函数体内不希望改变参数的值
- const 修饰的函数返回值表示返回值不可改变,多用于返回指针的情况。
小贴士:C 语言中的字符串字面量存储于只读存储区中,在程序中需要使用 const char* 指针。
const char* s = “hello world”;
实例分析:const 修饰的函数参数与返回值
//9-3.c
#include<stdio.h>
const char* f(const int i){
i = 5;
return "hello world";
}
int main(){
char* p = f(0);
printf("%s\n", p);
p[6] = '_';
printf("%s\n", p);
return 0;
}
编译结果如下:编译器提示,变量 i 是只读变量,不能修改。函数 f() 返回的是一个 const 类型的指针,将其赋值给一个普通指针,有修改的可能,给出警告。
所以,这里不能修改变量 i 的值;应该将指针 p 改为 const char* p;并且不能修改指针 p 指向的内容。
2、深藏不露的 volatile
volatile 平时使用较少,这里就来说一下这个关键字的作用。
volatile 可以理解为”编译器警告指示字“,什么意思呢,就是告诉编译器必须每次去内存中取变量值,防止编译器对代码进行优化。我们看一个例子:
对上面的代码,编译器在编译的时候发现 obj 没有被当作左值使用,因此会”聪明“的直接将 obj 替换成 10,而把 a 和 b 都赋值为10。
这样做会不会有问题呢,看似没有修改 obj,但是在多线程的程序中,或者在需要处理中断请求的程序中,不能保证其他线程或者中断请求对这块内存进行修改,在对 b 赋值的时候,可能 obj 的值已经改变了,所以 b 得到的值是不正确的。
所以,volatile 主要修饰可能被多个线程访问的变量,也可以修饰可能被为止因素更改的变量,比如中断处理。
小问题:const volatile int i = 0;
变量 i 具有什么样的特性,编译器如何处理这个变量?
解析:首先,变量 i 是 int 型的变量,每次对 i 操作都是对直接去内存中取,并且这个变量是只读的。
3、小节
1、const 使得变量具有只读属性
2、const 不能定义真正意义上的常量
3、const 将具有全局生命周期的变量存储在只读存储区
4、volatile 强制编译器减少优化,必须每次从内存中取值