一、总结

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修饰函数返回值表示返回值不可更改,多用于返回指针情况

#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是存储在局部变量区域,所以可以通过指针进行更改

讲的很啰嗦,希望不要见怪

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

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

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

  2. C语言const和volatile关键字

    这部分内容比较简单,我这里直接先做总结,然后通过写三个测试代码,体会其中的关键点 一.总结       1.const使得变量具有只读属性(但是不一定就是不能更改) 2.const不能定义真正意义上的 ...

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

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

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

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

  5. 自顶向下彻底理解 Java 中的 volatile 关键字

    标题 neta 自<计算机网络自顶向下> 思维导图 volatile 在 Java 中被称为轻量级 synchronized.很多并发专家引导用户远离 volatile 变量,因为使用它们 ...

  6. [C#.NET 拾遗补漏]10:理解 volatile 关键字

    要理解 C# 中的 volatile 关键字,就要先知道编译器背后的一个基本优化原理.比如对于下面这段代码: public class Example {public int x;public voi ...

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

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

  8. java volatile关键字使用

    1.为什么要使用volatile关键字? 先看下面的代码: //线程1 boolean stop = false; while(!stop){doSomething(); }//线程2 stop = ...

  9. C语言volatile 关键字

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

最新文章

  1. [转]c# 泛类型(泛型) 以及强类型与弱类型的 理解及优化
  2. 合并两个对象 java_在Java中合并两个对象列表8
  3. Python脱产8期 Day09 2019/4/23
  4. 《大咖讲Wireshark网络分析》目录—导读
  5. 喜马拉雅 Apache RocketMQ 消息治理实践
  6. Python中的无序集合(set)
  7. QlikView线图高亮选择尺寸
  8. 线性代数与MATLAB2
  9. 总结:常用的 Python 爬虫技巧
  10. 细说static关键字及其应用
  11. C语言数据结构——查找(检索)
  12. 计算机一级wpsoffice知识,全国计算机一级WPSOffice考试试题
  13. VS SP6补丁安装失败解决方法(Visual Studio 6.0 Service Pack 6 Setup was not completed successfully.)
  14. 【Codeforces Round #531 (Div. 3) F. Elongated Matrix】状压DP
  15. 第13周 《C语言及程序设计》实践参考——定期存款利息计算器
  16. HTML中视频的压缩方式,快速将视频压缩到最小的技巧!
  17. android 关机界面修改,修改Android关机界面
  18. Cordova各种事件
  19. 【Ethercat机器人控制系统开发】倍福Twincat入门教程
  20. 微软自带杀毒工具关闭

热门文章

  1. k8s安装dashboard及账号密码登陆
  2. 希腊字母书写以及发音,常用场景
  3. 怎样用计算机给ipd传电影,怎样不使用iTunes将电影导入iPad?
  4. Code With Me插件,IDEA多人协作
  5. 经常性无法访问某些国内网站的问题(by quqi99)
  6. sgu482 Impudent Thief (动态规划)
  7. 形态学-----细化
  8. 小程序-实现左右菜单联动功能
  9. 【网页制作】CSS基本选择器讲解(附讲解视频)
  10. 随时随地掌上邮,飞邮Android版邮件客户端正式提供试用