C和C++里的const常量、volatile修饰符的深层次说明
目录
一、写在前面
二、分析C和C++中const常量被修改后值的状态
2.1 C中const常量被修改后值的状态
2.2 C++中const常量被修改后值的状态
2.3 C和C++中const常量被修改后值的状态分析结论
三、添加volatile修饰后,分析C和C++中const常量被修改后值的状态
3.1 添加volatile修饰后,C中const常量被修改后值的状态
3.2 添加volatile修饰后,C++中const常量被修改后值的状态
3.3 添加volatile修饰后,C和C++中const常量被修改后值的状态分析结论
四、针对const常量在C和C++里分析volatile修饰前后值被修改的状态的结论
4.1 C中
4.2 C++中
五、原因
5.1 C中对const没有优化,C++存在const优化
5.2 局部const常量和全局const常量
5.3 借用评论区的说法
5.4 volatile优化
一、写在前面
1. 由于涉及到编译器层面,所以不同编译器结果可能不一样。本文使用的VS编译器。
2. 针对const修饰的整型常量:
const int local = 10;
3. 常量本质上也是一个变量,是变量就会有地址;
因为直接修改const常量是不允许的(编译就会报错),所以只能通过指针修改其地址上的值来试验。如下:
这里注意下,用vs复制一个文件到另一个文件运行(比如这里程序都是一样的,一个在c运行,一个在cpp运行,复制过去不清理解决方案,运行的其实还是上一个程序),但是编译器选择不同时,需要重新生成解决方案,不然运行的还是上一次的程序。
4.分析的问题及结论
const变量被修改后,常量本身值的修改情况 | const变量被修改后,常量对应的内存空间中的值的修改情况 | |
---|---|---|
C环境下 | 会被修改 | 会被修改 |
C++环境下 | 不会被修改 | 会被修改 |
const变量被修改后,常量本身值的修改情况 | const变量被修改后,常量对应的内存空间中的值的修改情况 | |
---|---|---|
C环境下 | 会被修改 | 会被修改 |
C++环境下 | 会被修改 | 会被修改 |
5.整体代码
c部分:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(void)
{const int local = 10;printf("修改前:\n");printf("修改前local本身的值 : %d \n", local);printf("修改前local的地址: %x \n", &local);int *ptr = (int*)&local;*ptr = 100;printf("修改后:\n");printf("修改后local本身的值: %d \n", local);printf("修改后local对应的内存空间中的值:: %d \n", *ptr);printf("修改后local的地址: %x \n", &local);printf("\n\n");printf("测试添加volatile修饰符:\n");/****** 添加volatile避免编译器的优化 ********/const volatile int local2 = 10;printf("修改前:\n");printf("修改前local2本身的值 : %d \n", local2);printf("修改前local的地址: %x \n", &local2);int *ptr2 = (int*)&local2;*ptr2 = 100;printf("修改后:\n");printf("修改后local2本身的值: %d \n", local2);printf("修改后local2对应的内存空间中的值:: %d \n", *ptr2);printf("修改后local2的地址: %x \n", &local2);return 0;
}
输出结果:
C++部分
#include <iostream>
#include<stdio.h>
using namespace std;int main(){const int local = 10;printf("修改前:\n");printf("修改前local本身的值 : %d \n", local);printf("修改前local的地址: %x \n", &local);int *ptr = (int*)&local;*ptr = 100;printf("修改后:\n");printf("修改后local本身的值: %d \n", local);printf("修改后local对应的内存空间中的值:: %d \n", *ptr);printf("修改后local的地址: %x \n", &local);printf("\n\n");printf("测试添加volatile修饰符:\n");/****** 添加volatile避免编译器的优化 ********/const volatile int local2 = 10;printf("修改前:\n");printf("修改前local2本身的值 : %d \n", local2);printf("修改前local的地址: %x \n", &local2);int *ptr2 = (int*)&local2;*ptr2 = 100;printf("修改后:\n");printf("修改后local2本身的值: %d \n", local2);printf("修改后local2对应的内存空间中的值:: %d \n", *ptr2);printf("修改后local2的地址: %x \n", &local2);return 0;
}
输出结果:
二、分析C和C++中const常量被修改后值的状态
这里讨论的值指常量本身的值(local)和常量对应的内存空间上的值(*ptr)。
2.1 C中const常量被修改后值的状态
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(void)
{const int local = 10;printf("修改前:\n");printf("修改前local本身的值 : %d \n", local);printf("修改前local的地址: %x \n", &local);int *ptr = (int*)&local;*ptr = 100;printf("修改后:\n");printf("修改后local本身的值: %d \n", local);printf("修改后local对应的内存空间中的值:: %d \n", *ptr);printf("修改后local的地址: %x \n", &local);return 0;
}
运行结果如下:
注意输出中选中区。
结论:通过指针的方式修改常量对应的内存空间中的值,常量本身的值(local)会被修改,常量对应的内存空间中的值(*ptr)也会被修改;
2.2 C++中const常量被修改后值的状态
代码如下:
#include <iostream>
#include<stdio.h>
using namespace std;
/***为了节省时间 输出之间用的printf没用cout****/int main(){const int local = 10;printf("修改前:\n");printf("修改前local本身的值 : %d \n", local);printf("修改前local的地址: %x \n", &local);int *ptr = (int*)&local;*ptr = 100;printf("修改后:\n");printf("修改后local本身的值: %d \n", local);printf("修改后local对应的内存空间中的值:: %d \n", *ptr);printf("修改后local的地址: %x \n", &local);return 0;
}
运行结果如下:
注意输出中选中区。
结论:通过指针的方式修改常量对应的内存空间中的值,常量本身的值(local)不会被修改,常量对应的内存空间中的值(*ptr)会被修改;
2.3 C和C++中const常量被修改后值的状态分析结论
所以,局部const常量的值在C++和C里面是否能修改的结果是不一样的。在C里面常量本身的值和内存空间对应的值都会变,C++里常量本身发值不会变,但是对应的内存空间的值会变。原因在后面会说。
这里说的是局部,全局const是什么会在后面说。
三、添加volatile修饰后,分析C和C++中const常量被修改后值的状态
这里讨论的值指常量本身的值(local)和常量对应的内存空间上的值(*ptr)。
3.1 添加volatile修饰后,C中const常量被修改后值的状态
代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>int main(void)
{const int local = 10;printf("测试添加volatile修饰符:\n");/****** 添加volatile避免编译器的优化 ********/const volatile int local2 = 10;printf("修改前:\n");printf("修改前local2本身的值 : %d \n", local2);printf("修改前local的地址: %x \n", &local2);int *ptr2 = (int*)&local2;*ptr2 = 100;printf("修改后:\n");printf("修改后local2本身的值: %d \n", local2);printf("修改后local2对应的内存空间中的值:: %d \n", *ptr2);printf("修改后local2的地址: %x \n", &local2);return 0;
}
运行结果如下:
注意输出中选中区。
结论:添加volatile修饰后,通过指针的方式修改常量对应的内存空间中的值,常量本身的值(local)会被修改,常量对应的内存空间中的值(*ptr)也会被修改;也就是说volatile修饰符在这里看不出是否跳过优化。
3.2 添加volatile修饰后,C++中const常量被修改后值的状态
代码如下:
#include <iostream>
#include<stdio.h>
using namespace std;
/***为了节省时间 输出之间用的printf没用cout****/int main(){const int local = 10;printf("测试添加volatile修饰符:\n");/****** 添加volatile避免编译器的优化 ********/const volatile int local2 = 10;printf("修改前:\n");printf("修改前local2本身的值 : %d \n", local2);printf("修改前local的地址: %x \n", &local2);int *ptr2 = (int*)&local2;*ptr2 = 100;printf("修改后:\n");printf("修改后local2本身的值: %d \n", local2);printf("修改后local2对应的内存空间中的值:: %d \n", *ptr2);printf("修改后local2的地址: %x \n", &local2);return 0;
}
运行结果如下:
注意输出中选中区。
结论:添加volatile修饰后,通过指针的方式修改常量对应的内存空间中的值,常量本身的值(local)会被修改,常量对应的内存空间中的值(*ptr)也会被修改;也就是说volatile修饰符在这里成功避免编译器优化。
3.3 添加volatile修饰后,C和C++中const常量被修改后值的状态分析结论
因为在C里面,const常量的值都能被修改,所以volatile无法提现;在C++里volatile成功避免了优化。
四、针对const常量在C和C++里分析volatile修饰前后值被修改的状态的结论
4.1 C中
无论是否进行volatile修饰,常量对应内存空间中的值(*ptr)都会被修改;
无论是否进行volatile修饰,常量本身的值(local)都会被修改。
4.2 C++中
无论是否进行volatile修饰,常量对应内存空间的值(*ptr)都会被修改;
如果不采用volatile修饰const常量,常量本身的值(local)不会被修改,volatile修饰后常量本身的值(local)会被修改。
五、原因
5.1 C中对const没有优化,C++存在const优化
c++中用const定义了一个常量后,不会分配一个空间给它,而是将其写入符号表(symbol table),这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。
通过指针的方式修改常量对应的内存空间中的值时,这种修改不会影响到常量本身的值,因为用到该常量(local)的时候,编译器根本不会去进行内存空间的读取。这就是c++的常量折叠(constant folding),即将const常量放在符号表中,而并不给其分配内存,编译器直接进行替换优化。除非需要用到local的存储空间的时候,编译器迫不得已才会分配一个空间给local,但之后local的值仍旧从符号表中读取,不管local的存储空间中的值如何变化,都不会对常量local产生影响(即常量放在符号表,无论是否对其进行操作,常量本身的使用都是从符号表中去取。所以就算从内存空间对其进行了修改,内存空间的值变化了,但是常量的值还是从符号表中拿,值还是没变)。
示例:
int main(){const int local = 2;int* ptr = (int*)(&local);*ptr = 30;cout<<&local<<endl; cout<<ptr <<endl; cout<<local<<endl;cout<<*ptr <<endl;
}
输出:
0x28ff08
0x28ff08
2
30
在C中却不是这样,C没有constant folding的概念,用const定义一个常量的时候,编译器会直接开辟一个内存空间存放该常量,不会进行优化,所以从内存空间对其进行了修改,内存空间的值变化了,常量本身的值也就变化了(跟变量一样,只不过常量不能直接用常量名二次赋值或初始化)。
示例:
int main()
{const int local = 2;int* ptr = (int*)(&local);*ptr = 30;printf("%x\n",&local);printf("%x\n",ptr);printf("%d\n",local);printf("%d\n",*ptr);return 0;
}
输出:
28ff18
28ff18
30
30
5.2 局部const常量和全局const常量
1. 针对定义在函数内部(包括main函数)的const常量,不管是c还是c++,本质上都只是将其当成一个普通的局部变量来对待,都只是在栈上分配空间。所以const根本就不能起到阻止修改其内存空间的作用,一个合法的强制类型转换就可以轻松搞定。C++比C好的地方就在于使用了constant folding的机制,使得常量的值跟对应的内存空间无关,从而保护了该常量值。
2. 对于全局const常量(定义在main之外),在c和c++中如果我们仍然用int *ptr = (int*)(&local);这种方法来修改它内存中的值,编译时不会报错(这里看编译器,gcc不报错,vs编译时就会报错),但是运行时会报段错误,因为local是放在只读的全局数据区中,修改该区中的数据会引发段错误,也就是不能进行修改。
3. 总结来说,全局const常量放在常量只读区,C和C++里都没法修改;局部const常量放在栈上,能够通过操作内存的方式(int *ptr = (int*)(&local))修改其内存空间中的值。
注意这里局部const常量被修改的是常量对应内存空间的值,常量本身的值是否改变取决于是C还是C++,C++里有优化,常量本身的值不会变,而C没有优化,所以常量本身的值会被修改。
5.3 借用评论区的说法
在C中,const修饰的变量,是指不可修改的变量(旨在提示程序员)。如何使这个变量不可修改呢?在编译期间对这个变量进行检查,如果有语句试图修改这个变量,就报编译错误,使编译不通过。虽然如此,但仍然可以通过指针间接修改const变量的值(绕过编译器检查)来证明这是一个变量。作为函数参数,是限定函数只对该参数(这里的参数是引用类型,引用使用址传递效率高)进行读,而不允许写(避免误操作,因为修改参数引用会直接改变原数据)。
在C++中,常量就真的是常量了。
这也就是为什么我们说c里面常量只是只读,并不是真正的常量的原因。
5.4 volatile优化
volatile 确实能避免编译器优化。
C和C++里的const常量、volatile修饰符的深层次说明相关推荐
- java中Volatile修饰符的含义
在java语言中:为了获得最佳速度,同意线程保存共享成员变量的私有拷贝.并且仅仅当线程进入或者离开同步代码块时才与共享成员变量的原始值进行对照. volatilekeyword的作用就是提示vm:对于 ...
- C++基础学习(02)--(数据类型,变量类型,变量作用域,常量,修饰符类型)
文章目录 目录 一. 数据类型 C++ 中的数据类型 typedef enumeration枚举类型 c++中变量类型 二.变量作用域 三.常量 四.修饰符类型 目录 一. 数据类型 C++ 中的数据 ...
- 开课吧Java课堂:Transient和volatile修饰符如何运用
Java定义了两类有趣的修饰符:transient和volatile,这些修饰符用来处理特殊的情况.如果用transient声明一个实例变量,当对象存储时,它的值不需要维持.例如: class T { ...
- 一文理解C语言中的volatile修饰符
一文理解C语言中的volatile修饰符 2019/12/2 FesianXu 前言 volatile修饰符是在嵌入式开发和多线程并发编程中常见的修饰符,理解其对于实践过程非常有帮助,此文参考了[1] ...
- 关于STM32库中 __IO 修饰符(volatile修饰符,反复无常的意思)
STM32例子代码中会有像这样的代码 static __IO uint32_t TimingDelay; 这里边的__IO修饰符不好理解,单从字面可以看出是为IO相关,查其标准库可以得知这个__IO ...
- 再谈C#里4个访问权限修饰符
想必大家对这个四个修饰符都已经很了解了,但是我在这里还是要说,是为什么呢?因为每个人对它的理解不一样,我写出来对自己是个温故而知新,对大家是个分享,希望有什么不对的或需要谈论的地方大家指出来. C#里 ...
- C语言 const、volatile、const volatile限定符理解
在C语言经常会用到变量类型限定符const和volatile,但是const和 volatile也可以一起使用.下面就一次分析一下这三种情况的使用方法. 1.const 从字面意思理解,意思就 ...
- volatile限定符
备注:volatile的确切含义与机器有关,只能通过阅读编译器文档来理解.要想让使用了volatile的程序在移植到新机器或新编译器后仍然有效,通常需要对该程序进行某些改变. 直接处理硬件的程序常常包 ...
- Java并发编程(5):volatile变量修饰符—意料之外的问题(含代码)
volatile用处说明 在JDK1.2之前,Java的内存模型实现总是从主存(即共享内存)读取变量,是不需要进行特别的注意的.而随着JVM的成熟和优化,现在在多线程环境下volatile关键字的使用 ...
最新文章
- 稀疏矩阵快速转置核心代码
- 字符串数字转换成对应的Double数值
- 全球及中国皮肤晒黑喷雾行业销售模式及动态盈利分析报告2021年版
- LPS25HB 气压计 的嵌入式程序开发
- skywalking告警相关配置
- Android BroadcastReceiver中播放提示语音有时失效问题
- mybatisplus 一次性执行多条SQL语句
- Oracle数据库重启后密码失效的问题(r12笔记第91天)
- 华为matebook和linux版本区别,华为MateBook14Linux版本开箱评测 | 从来没有让我们失望!...
- POJ2033 LA3078 HDU1508 ZOJ2202 Alphacode【DFS+DP】
- 设计模式解密(9)- 装饰者模式
- 原创 | 一文了解人工智能对精准扶贫的作用及数字乡村建设现状
- Tek DPO2024B示波器和电流探头A622的使用
- 数据库报错create connection SQLException,............ errorCode 1129, state HY000
- css3的坐标轴是相对于电脑屏幕还是物体自身?
- 我丈母娘家的小店竟然被Dos攻击了
- 计算机会计和传统手工会计的区别,电算化会计与传统手工会计的区别
- java门禁系统项目开发实现
- Oracle时代:Sun开源拳头产品迎不同未来
- 《Unity5.x从入门到精通》读书笔记(二)