【C语言进阶剖析】9、const 和 volatile 分析

1、const 解析

const 关键字有如下特点:

  1. const 修饰的变量本质上还是一个变量,只不过这个变量是只读的
  2. const 修饰的局部变量在栈上分配空间
  3. const 修饰的全局变量在全局数据区分配空间
  4. 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 强制编译器减少优化,必须每次从内存中取值
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值