这部分内容比较简单,我这里直接先做总结,然后通过写三个测试代码,体会其中的关键点

一、总结
      1、const使得变量具有只读属性(但是不一定就是不能更改)

2、const不能定义真正意义上的常量(因为有的用const定义的变量,仍然可以更改)

3、const将具有全局生命期的变量存储于只读存储区(这个是对现代编译器是这样的,但是对ANSI编译器,仍然可以更改)

4、volatile强制编译器减少优化,必须每次从内存中取值

5、const修饰的变量不是一个真的常量,它只是告诉编译器该变量不能出现在赋值符号的左边

6、在现在C编译器中,修改const全局变量将导致程序崩溃

7、c语言中字符串字面量存储于只读存储区中,在程序中需要使用const char*指针(这句话的意思就是用const char*修饰的字符串字面量时(包括局部和全局的字符串字面量),

    字符串字面量是存储在全局只读存储区的,不能更改,更改会导致程序崩溃或者段错误)

8、const修饰函数参数表示在函数体内不希望改变参数的值(注意:这里是不希望,那到底能不能更改,这得分情况)

9、const修饰函数返回值表示返回值不可更改,多用于返回指针情况

二、下面通过几个测试代码体会上面结论(平台:Ubuntu10 gcc 编译器)
        第1个例子是const修饰变量情况

#include <stdio.h>const int g_cc = 4;int main()
{const const int cc = 0x01;int* p = (int*)&cc; printf("cc = %d *p = 0x%x\n",cc,*p);//cc = 2; //编译通过,运行错误,因为cc被定义成const局部变量,不能出现在赋值符号左边,运行时导致程序段错误*p = 3; //编译和运行都通过,因为cc是局部变量,所以不管是ANSI还是现代GCC编译器都可以更改,同时也说明了用const修饰变量只是说明这个变量不能出现在赋值符号的左边,但是依然可以更改,但是假如如果cc是全局变量,那就不一定了,如果是ANSI编译器是可以更改的(你可以用BCC编译器试下,BCC就是早期的ANSI编译器)因为早期编译器把const修饰的变量还是存储在全局数据区可以更改,如果是现在的VC或者GCC编译器是不可以更改的,因为现代编译器把const修饰的全局变量存储在全局只读存储区中,更改会出错
printf("cc = %d *p = %d\n",cc,*p); p = (int*)&g_cc;printf("g_cc = %d *p = %d\n",g_cc,*p);//*p = 5; //编译通过,运行错误,因为g_cc被定义成const全局变量,又因为GCC属于现代编译器所以g_cc被分配到全局只读存储区,不能更改,更改导致段错误//printf("g_cc = %d *p = %d\n",g_cc,*p);return 0;
}

上面的代码你可以把屏蔽部分代码打开自己调试,其实是不能运行的,代码注释解释的很清楚,这里不说了,看下输出结果:

其实我开始调试时cc变量的类型是unsigend char,出现了一个意外问题,你们看下输出,然后自己想下为什么?(这其实是指针类型问题,后面我会讲这个问题)

第2个例子是const修饰函数返回值、函数参数、字符串情况

注意:我会在程序里面提问18个问题,你看看你们能不能回答出来答案

#include <stdio.h>const unsigned char *s1 = "G_hello world";unsigned char a = 9;unsigned char* fun(const unsigned char s,const unsigned char *str)
{unsigned char *p = (unsigned char*)&s;//s = 2; //(1)为什么编译错误*p = 1; //(2)p指针指向s,但是s是const修饰的,不能更改,但是这里为啥能够更改printf("s = %d *p = %d\n",s,*p);p = (unsigned char*)str;//*p = '_'; //(3)为什么编译通过,运行段错误
printf("str = %s\np = %s\n",str,p);return "ABCDEF GHIJK"; //(4)这种定义的字符串字面量和使用字符指针指向字符串字面量有啥区别,还是一样的?
}int main()
{const unsigned char *s2 = "Hello world";//(5)s2指针指向的内容能不能更改?unsigned char *s3 = "LMNOP ORST"; //(6)你们看到s3比s2少了一个const,那编译会不会出错? s3指向内容能不能更改呢?那和s2有啥区别呢?unsigned char *s4 = "LMNOP ORST"; //(7)s4和s3指向的内容都是一样的,那他们地址是不是一样的呢?unsigned char s5[] = "LMNOP ORST"; //(8)s5和s3一个是数组,一个是指针,那他们有啥区别呢?而且他们内容也是相同的,那他们的地址是不是也是一样的呢const unsigned char s6[] = "LMNOP ORST";//(9)s6比s5多了一个const,多了这个导致有啥区别么?const unsigned char i = 2;const static unsigned char j = 3; //(10)j比i多了一个static,多了这个导致有啥区别么?unsigned char *pc = fun(i,s2); printf("&i = %p &a = %p\n",&i,&a);printf("&j = %p j = %d\n",&j,j);printf("s1 = %p s2 = %p\n",s1,s2);printf("s3 = %p s4 = %p\n",s3,s4); printf("s5 = %p s5 = %s\n",s5,s5); printf("s6 = %p s6 = %s\n",s6,s6); printf("pc = %p\npc = %s\n",pc,pc);//(11)通过观测这么多变量,字符指针,数组你发现什么规律没(从地址去观察)//j = 4; //编译出错,我们通过终端打印发现j是存储在全局只读区域中,所以不能更改pc = &j; //编译出现警告,运行通过,因为指针可以指向任何地方//*pc = 5; //编译通过,运行段错误,因为pc指向的是全局只读区域,所以不能更改//*pc = '!'; //(12)编译通过,为什么运行段错误//*s2 = '_'; //(13)为什么编译错误pc = s2; //编译出现警告,因为类型不一样//*pc = '$'; //(13)编译通过,为什么运行段错误//*s3 = 'A'; //(14)编译通过,为什么运行段错误pc = s3;//*pc = '_'; //(15)编译通过,为什么运行段错误
printf("更改前:s5 = %s\n",s5);s5[0] = 'A';pc = s5;*(pc + 1) = 'B';printf("更改后:pc = %s s5 = %s\n",pc,s5);printf("更改前:s6 = %s\n",s6);//s6[0] = 'A'; //(16)为什么不能更改pc = &s6[0];*pc = 'A'; //(17)为什么用一个指针却可以更改s6呢,再从地址观察s5和s6,有啥发现printf("更改后:pc = %s s6 = %s\n",pc,s6);return 0;
}

我们看下终端输出:

现在回答上面的17个答案:

(1):因为被const关键字修饰变量,不能出现在赋值符号左边,所以编译出错

(2):因为用const定义变量只是告诉编译器不能出现赋值符号左边,但是本质还是变量,这里就是局部变量,还是可以通过指针修改它的值

(3):编译肯定通过,因为p是指针当然可以指向任何地方,运行错误是因为p指针指向的是字符串字面量,而字符串字面量是存储在全局只读存储区,所以运行错误(具体为什么是全局只读区域,后面我在(11)提问里面会说)

(4):其实是一样的,因为我们从终端地址发现他们都在0x80487XXH内存区域里面,而这个区域就是全局只读区域,都是不能更改的(具体为什么是全局只读区域,后面我在(11)提问里面会说)

(5):不能更改的,因为定义的字符串指针是指向字符串字面量,而字符串字面量存储的区域是全局只读区,所以不能更改,有的人问,你怎么知道是全局只读区域,这个在(11)的提问里面回答这个问题

(6):编译是不会出错的(包括编译和执行),s3指向的内容也是不能更改的,这个在后面我会给你验证的,其实你从终端打印的地址也能看出来的,因为你发现他们都是存储在0x80487XX的地址区域,而这个区域都是全局只读区域,所以不能更改,还有和s2有什么区别,其实我认为是没有区别的,因为他们都不能更改,而且存储的区域也都一样,所以我认为没有区别

(7):通过终端打印我们发现地址居然一样,编译器居然为了节省空间(我猜想的),只存储一个"LMNOP ORST",当然他们都是存在只读内存空间,不能更改,比较安全,如果是可更改空间,那可就出大事了,修改其中一个内容值,另外一个变量内容也跟着更改了

(8):首先s3是字符指针,指向内容是一个字符串字面量,而且s3指向内容的区域是全局只读区域,所以不能更改,而s5是数组是可以更改的,而且s5是局部的,也就是存储在栈中,临时分配的内存,函数执行完释放掉,同时通过终端打印我们也发现s3和s5内存地址也是完全不一样的,相差很多,因为一个是全局只读区域,另一个是局部内存区域(就是栈)

(9):s6和s5的区别是,s5可以直接更改,就是s[0] = 'A';,而s6是不能直接更改的,s6[0] = 'A'编译器在编译时就会报错,但是他们都是存储在局部内存区域(就是栈),这个区域是可以通过指针进行更改的,所以s6还是可以更改的,通过终端打印发现他们地址也很接近

(10):j和i的区别是,j存储在全局只读区域,不能更改,i是存储在栈中,是可以更改的,但是不能直接更改,必须通过指针进行更改,通过终端打印也发现,j的地址和s1、a的地址都很接近,所以j肯定是全局只读区域(为什么是只读区域,后面我会验证,因为经过验证它不能更改)

(11):总结:

首先我们肯定知道s1肯定是全局区域,又由于s1不能更改(这个我没写进程序里面,你们可以自己去验证下, 其实真的不能更改),所以s1存储在全局只读区域,又因为s1跟j、s2、s3、s4、pc,所以这些变量存储的内容都是存储在全局只读区域内,不能更改,但是你们发现没,a变量肯定也是全局变量,但是它确是可以更改的,所以a和s1地址肯定不一样,通过终端打印发现,他们确实不挨着,而且相差也不是很多,因为他们都在全局区域内

其次:通过这个例子我们知道用const unsigned char*定义的指针指向了字符串字面量是不能更改的,而且是存储在全局只读存储区的(这里记住,即使没有const也是全局只读区域,s3就是这样的),要是也想把局部变量也定义到全局只读存储区中,需要用const static关键字(比如这里的j变量就是),而且我们还发现,字符串指针如果只向内容是一样的,编译器居然为了省空间,地址居然是一样的

再次: 字符串指针和数组,是有区别的,他们只向的内容存储的区域不一样,字符串指针是全局只读区域,而数组是栈中,可以更改,虽然有的加了onst但是通过指针还是可以更改

(12):因为pc指针指向fun函数返回的内容是存储在全局只读区域,不能更改,所以运行错误

(13):因为const定义变量是不能出现在赋值符号左边,而且s2指针,指向的内容是字符串字面量,是存储在全局只读区域内,是不能更改的

(14):因为s3指针,指向的内容是字符串字面量,是存储在全局只读区域内,是不能更改的

(15):同上

(16):因为s6是const关键字定义的局部变量,是不能出现在赋值符号左边,但是可以更改,不能这样直接更改,需要用指针进行更改

(17):通过终端打印发现s5和s6地址很接近,因为他们都是局部变量,存储在栈中,但是因为s6是用const关键字定义变量,是不能出现在赋值符号左边的,但是又因为s6是存储在局部变量区域,所以可以通过指针进行更改

volatile影响编译器编译的结果,指volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)。

例如: 
volatile int i=10; 
int j = i; 
... 
int k = i;

volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,

它会自动把上次读的数据放在k中。而不是重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问,不会出错。

一般用在1.并行设备的硬件寄存器,如状态寄存器,2.中断服务程序用于检测中断的变量,3.多线程被线程共享的变量。

本文主要参考了"狄泰软件C进阶视频教程”

原文:https://blog.csdn.net/liuchunjie11/article/details/80333224

转载于:https://www.cnblogs.com/cyyljw/p/10882447.html

C语言const和volatile关键字相关推荐

  1. C语言 const、volatile、const volatile限定符理解

      在C语言经常会用到变量类型限定符const和volatile,但是const和 volatile也可以一起使用.下面就一次分析一下这三种情况的使用方法. 1.const   从字面意思理解,意思就 ...

  2. 你真的理解了const和volatile关键字么?(我看不一定)

    一.总结 1.const使得变量具有只读属性(但是不一定就是不能更改) 2.const不能定义真正意义上的常量(因为有的用const定义的变量,仍然可以更改) 3.const将具有全局生命期的变量存储 ...

  3. register,static,extern,const,typedef,volatile关键字

    一.register 用register修饰的作用: 请求编译器尽可能(CPU寄存器资源有限)将变量的值保存在CPU内部寄存器中,省去了CPU从内存中抓取数据的时间,提高了程序的运行效率. 何时用re ...

  4. 计算长方形体积c语言const,c语言const

    <OOC>笔记(1)--C语言const.static和extern的用法 笔记(1)--C语言const.static和extern的用法 C语言中const关键字用法不少,我只喜欢两种 ...

  5. static、const、volatile等关键字作用

    目录 一.需要明白c语言中对象的一些属性 1.C语言中内存分配 2.变量的作用域 3.链接属性 4.存储期 二.static.const.volatile关键字作用 1.static关键字作用 2.c ...

  6. C语言volatile 关键字

    什么是volatile关键字 volatile用于声明一个变量,告诉编译器该变量值容易发生改变,在编译.读取.存储该变量的时候都不要做任何优化,因此编译后的程序每次需要存储或读取这个变量的时候,都会直 ...

  7. 如何理解 JAVA 中的 volatile 关键字

    如何理解 JAVA 中的 volatile 关键字 最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂. ...

  8. 深入理解Java中的volatile关键字

    在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性.可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized ...

  9. volite java_如何理解 JAVA volatile 关键字

    最近在重新梳理多线程,同步相关的知识点.关于 volatile 关键字阅读了好多博客文章,发现质量高适合小白的不多,最终找到一篇英文的非常通俗易懂.所以学习过程中顺手翻译下来,一方面巩固知识,一方面希 ...

最新文章

  1. FZU 2297 Number theory【线段树/单点更新/思维】
  2. Roller5.0.3安装配置部署 step by step
  3. 微信小程序 setData动态设置数组中的数据
  4. ios开发 循环引用 检测_iOS开发——Block引起循环引用的解决方案
  5. 关于MYSQL ERROR1045 报错的解决办法
  6. 字符串是通过“引用”传递的
  7. python实现自动登录网页用户名密码_Python使用selenium实现网页用户名 密码 验证码自动登录功能...
  8. 进入linux jed文本编辑怎么退出,尝试将 Jed 作为你的 Linux 终端文本编辑器 | Linux 中国...
  9. 2020最全蓝牙耳机盘点:年会最值得选择的十大优秀蓝牙耳机品牌
  10. phpstudy的基本使用方法
  11. 体验谷歌菜市场镜像版
  12. CentOS安装Nacos后,输入默认用户名和密码nacos/nacos,提示“用户名或密码错误”
  13. echarts水球图-动态波纹百分比数据显示
  14. 大数据教程:数据可视化(ECharts)
  15. 【重识云原生】第六章容器基础6.4.10.5节——Statefulset原理剖析
  16. 【Codeforces Round #551 (Div. 2) C. Serval and Parenthesis Sequence(Java版)
  17. 12--CSS导航栏(知识点复习)
  18. 《苏菲的世界》读书笔记
  19. 微软和谷歌又要“打”起来了!网友:太好了
  20. 百城价格房价周期和郑州、武汉房价比较分析

热门文章

  1. elasticsearch入门笔记
  2. 针对此次疫情的防控建议
  3. lisp语言如何画小红点_用AutoLISP语言编程实现参数化绘图
  4. “智慧高速公路”建设应重点提升“智慧化的出行服务”
  5. matlab_plot实时画点
  6. 按如下函数原型编程从键盘输入一个m行n列的二维数组,然后计算数组中元素的最大值及其所在的行列下标值。其中,m和n的值由用户键盘输入。已知m和n的值都不超过10。
  7. win10系统 - U盘启动盘制作教程
  8. 值得收藏的机器学习资源
  9. 【渝粤教育】电大中专跨境电子商务理论与实务 (24)作业 题库
  10. 讲师征集令 | Apache DolphinScheduler Meetup分享嘉宾,期待你的议题和声音!