目录
一、写在前面
二、分析C和C++中const常量被修改后值的状态
2.1 C中const常量被修改后值的状态
2.2 C++中const常量被修改后值的状态
2.3C和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.分析的问题及结论
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.3C和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;}
输出:
0x28ff080x28ff08230
在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;}
输出:
28ff1828ff183030
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 确实能避免编译器优化。