C++中如何修改const变量你知道吗

目录
  • 一、结论
  • 二、分析
    • 1.g++
    • 2.devc++
    • 3.VC++6.0
      • (1)添加volatile修饰符时,输出结果(程序代码同上):
      • (2)无volatile修饰符时。输出结果:
    • 4.VS2010
      • (1)添加volatile修饰符时,输出结果:
      • (2)无volatile修饰符时,输出结果:
  • 总结

一、结论

声明:不同于C语言的const变量修改问题(可以通过指针间接修改const变量的值),这里只讨论C++ 里的const。

C++ const 修饰符,表示常量,即如果以后保证不会修改则声明为const,否则若要修改,那一开始为什么还要声明为const呢?

根据C++标准,对于修改const变量,属于:未定义行为(指行为不可预测的计算机代码),这样一来此行为取决于各种编译器的具体实现(即不同编译器可能表现不同)。

故结论就是:不建议这么做!

但是,是的,但是,网上论坛、博客里均有有关如何修改const变量的方法,其不是依赖于某种具体的编译器,就是讲的欠考虑。

方法是在定义变量的时候加上volatile关键字(没有其他方法了吗(比如,const_cast ...)? 是的,目前为止,我只知道这种方法是可能的):

const volatile int i = 10;

:关于volatile这里不细讲,详见:volatile 关键字。考虑到volatile的重要性,后面自己也会写一篇关于volatile详解的文章。

二、分析

为了说明问题,下面在三种编译器环境下做几个小实验

1. g++

#include <stdio.h>
int main()
{
    const volatile int i = 10;
    int* pi = (int*)(&i);
    *pi = 100;
    printf("*pi: %d\n",*pi);
    printf("i: %d\n",i);
    printf("pi: %p\n",pi);
    printf("&i: %p\n", &i);
    return 0;
}

输出结果:

gdb查看其汇编代码命令:进入gdb,然后输入:disass main):

可以看出:输入*pi 和 i 时均是从堆栈(即内存)中取数的。

反例:把 volatile关键字去掉:

#include <stdio.h>
int main()
{
    const int i = 10;
    int* pi = (int*)(&i);
    *pi = 100;
    printf("*pi: %d\n",*pi);
    printf("i: %d\n",i);
    printf("pi: %p\n",pi);
    printf("&i: %p\n", &i);
    return 0;
}

输出结果:

由此可见:在没有volatile关键字修饰时,const 变量 i 的值时没有改变的。

运用gdb查看其汇编代码:

注意此时(没有加volatile修饰符),输出 变量 i 的值时直接将 0xa(10)值(从符号表中取出的)输出,即此处编译器进行了优化,没有从内存中读。

注意到:指针 pi 和 &i(i 的地址)值却是一样的。So ,Why?

这就是C++中的常量折叠:指const变量(即常量)值放在编译器的符号表中,计算时编译器直接从表中取值,省去了访问内存的时间,从而达到了优化。

而在此基础上加上volatile修改符,即告诉编译器该变量属于易变的,不要对此句进行优化,每次计算时要去内存中取数。

这里也有个小细节:每种编译器对volatile修饰符的修饰作用效果不一致,有的就直接“不理会”,如VC++6.0编译器(下面会讲到)。

2. dev c++

运行结果与1(g++)一致。

3. VC++ 6.0

(1)添加volatile修饰符时,输出结果(程序代码同上):

i 的值还是10,没有改变!这是为什么呢?不急,先看下其汇编代码:

注意:g++ 汇编代码的mov指令 与 VC++ 6.0的mov指令不同(传送方向相反)。

真相大白:虽然定义const变量的同时加上了volatile修饰符,但VC++ 6.0编译器还是进行了优化措施,输出 i 时 从编译器的符号表中取值,直接输出。

(2)无 volatile 修饰符时。输出结果:

i 的值没有改变,预期中。其汇编代码为:

结果与添加volatile时相同。

即在VC++6.0编译环境下,在const变量定义时添加volatile修饰符,与不添加效果是一样的。编译器都采取了优化(甚至把编译器优化选项关闭还是如此,有点恐怖...)。

4. VS 2010

再看下Microsoft编译器家族的高级版本:

(1)添加 volatile 修饰符时,输出结果:

i 的值被成功修改了!

(2)无 volatile 修饰符时,输出结果:

i 的值没有被修改。

故:不建议修改const变量的值,即使修改也要熟悉当前使用的编译器对于该 未定义行为 是如何解释的

总结

本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注我们的更多内容!

(0)

相关推荐

  • c++ 类中const成员变量的赋值方法

    在头文件的类的定义中定义了一个const成员变量c++ 规则: 1.类定义中不能进行初始化,因为头文件中类的定义只是一个声明,并没有分配真正空间,因此变量是不存在的,因此是不能赋值的. 2.const 定义的变量是不能赋值 这可如何是好,声明中不能赋值,声明完还不能赋值.又不能不赋值. 解决方案: 1.在构造函数后的参数初始化列表中初始化 2.将const变量同时声明为 static 类型进行初始化. Eg: #include <iostream> class CTestA { public:

  • C++ const引用、临时变量 引用参数详解

    C++引用-临时变量.引用参数和const引用 如果实参与引用参数不匹配,C++将生成临时变量.如果引用参数是const,则编译器在下面两种情况下生成临时变量: 实参类型是正确的,但不是左值 实参类型不正确,但可以转换为正确的类型 左值参数是可被引用的数据对象,例如,变量.数组元素.结构成员.引用和被解除引用的指针都是左值,非左值包括字面常量和包含多项式的表达式.定义一个函数 Double refcube(const double& ra) { Returnra*ra*ra; } double

  • c语言和c++语言中const修饰的变量区别浅析

    目录 c: 修饰全局变量: 修饰局部变量: c++: 修饰全局变量: 修饰局部变量: 总结: 在c语言中: 在c++语言中: 总结 c: 修饰全局变量: 用const修饰的全局变量是没有办法直接修改的,间接的修改也是不成功的(语法可以通过,但是编译运行的时候会报错.)(const只要是修饰全局变量,那么就会储存到常量区中,收到常量区的保护.) 修饰局部变量: 但是如果用const修饰局部变量,同样的也是没有办法直接修改的,但是是可以间接修改的. int main() { const int b

  • C++ const修饰变量和修饰函数介绍

    const修饰变量 关于const最常见的一个面试题是这样的:char *const和const char*有什么区别,大家都知道const修饰符代表的是常量,即const修饰的变量一旦被初始化是不能被更改的,这两个类型一个代表的是指针不可变,一个代表指针指向内容不可变,但具体哪个对应哪个,很多人一直搞不清楚. 有这样一个规律,const修饰的是它前面所有的数据类型,如果const在最前面,那么把它和它后面第一个数据类行交换.比如上面的const char*交换之后就是char const *,

  • 初识C++的const关键字,常量与常变量

    目录 C语言的const 1.const关键字 2.const修饰的只读变量 3.节省空间,避免不必要的内存分配,同时提高效率 4.使用cosnt修饰 4.1修饰一般变量 4.2修饰数组 4.3修饰指针 4.4修饰函数的参数 4.5修饰函数的返回值 C与C++中cosnt的区别 1.在C语言中 2.在C++语言中 3.cosnt修饰指针 3.1常量指针 3.2指针常量 3.3指向常量的指针常量 4.cosnt修饰二级指针 5.cosnt指针练习 总结 C语言的const 注:c语言部分cosnt

  • C++中如何修改const变量你知道吗

    目录 一.结论 二.分析 1.g++ 2.devc++ 3.VC++6.0 (1)添加volatile修饰符时,输出结果(程序代码同上): (2)无volatile修饰符时.输出结果: 4.VS2010 (1)添加volatile修饰符时,输出结果: (2)无volatile修饰符时,输出结果: 总结 一.结论 声明:不同于C语言的const变量修改问题(可以通过指针间接修改const变量的值),这里只讨论C++ 里的const. C++ const 修饰符,表示常量,即如果以后保证不会修改则声

  • C语言在头文件中定义const变量详解

    C语言在头文件中定义const变量详解 在头文件中定义const不会有多变量的警告或错误,如果该头文件被大量包含会造成rom空间的浪费. 通过查看*.i文件的展开呢,可以发现每个.i文件都会有相应的变量展开. 查看*.map文件,能查看到该变量的多个地址分配. 在预编译的时候如果在头文件定义了const变量,每一个包含该头文件的c文件都会将其展开,而在编译的时候不会报错,因为这符合语法规则,每一个包含这个头文件的*.c文件都会编译一次这个变量,分配一个新的地址,然后在链接的时候也不会报错,因为每

  • Linux中修改环境变量及生效方法

    在/etc/profile文件中添加变量[对所有用户生效(永久的)] 用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是"永久的". 要让刚才的修改马上生效,需要执行以下代码 source /etc/profile 以上这篇Linux中修改环境变量及生效方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

  • 在Linux操作系统中修改环境变量的方法

    方法一:在/etc/profile文件中添加变量[对所有用户生效(永久的)] 用VI在文件/etc/profile文件中增加变量,该变量将会对Linux下所有用户有效,并且是"永久的". 要让刚才的修改马上生效,需要执行以下代码 复制代码 代码如下: # source /etc/profile 方法二:在用户目录下的.bash_profile文件中增加变量[对单一用户生效(永久的)] 用VI在用户目录下的.bash_profile文件中增加变量,改变量仅会对当前用户有效,并且是&quo

  • ES6中let、const的区别及变量的解构赋值操作方法实例分析

    本文实例讲述了ES6中let.const的区别及变量的解构赋值操作方法.分享给大家供大家参考,具体如下: 声明:本文内容依照阮一峰老师ECMAScript 6 入门一书所总结. let和const 相同点 都存在块级作用域 都不存在变量声明提升 都会造成"暂时性死区" 在一个作用域下不可重复声明 不同点 const一旦声明必须立即赋值 const声明的变量指向的内存地址不得改动. 变量解构赋值 数组的解构赋值 解构成功 let [a, b, c] = [1, 2, 3]; 解构不成功

  • 详解Golang编程中的常量与变量

    Go语言常量 常量是指该程序可能无法在其执行期间改变的固定值.这些固定值也被称为文字. 常量可以是任何像一个整型常量,一个浮点常量,字符常量或字符串文字的基本数据类型.还有枚举常量. 常量是一样,只是它们的值不能自己定义后进行修改常规变量处理. 整型常量 一个整数文字可以是十进制,八进制,或十六进制常数.前缀指定基或基数:0x或0X的十六进制,0表示八进制,并没有为十进制. 一个整数文字也可以有一个后缀为U和L的组合,分别为无符号和长整型.后缀可以是大写或小写,并且可以以任意顺序. 这里是整数常

  • Lambda表达式里面修改外部变量问题

    先看下Lambda表达式里面修改外部变量问题 因为平台用的是JDK8,而且发现自己对那些新特性好像一点都不了解,就例如Lambda表达式,所以就开始对自己的代码进行改进了... 例如遍历Map,像我们正常遍历肯定是下面这样子的. String result = "select * from where id = '#userId#' and name = '#userName#'"; Map<String,String> sysParams = new HashMap<

  • 在Vuex中Mutations修改状态操作

    上篇是读取state,这篇是修改状态.即如何操作Mutations. 一. $store.commit( ) Vuex提供了commit方法来修改状态 1.store.js文件 const mutations={ add(state){ state.count++ }, reduce(state){ state.count-- } } 2.在button上的修改方法 <button @click="$store.commit('add')">+</button>

  • JavaScript中ES6规范中let和const的用法和区别

    ES6-- let 和 const 命令 引言 本文主要讲解ES6中变量的相关操作,变量的命名, 讲解 var. let . const 三者的区别 正文 ES6中的 let 和 const 都是用来声明变量的, 他们与 var 有所区别 let 命令 我们都知道在for循环中,我们命名的变量 i 一般都只是为了在这个循环中使用,才临时命名的, 我们希望循环结束后,这个变量就消失, 但是却相反,用 var 命名的变量,在 for 循环结束后并不会销毁,而会存在于全局中. for(var i=0;

  • Vue CLI中模式与环境变量的深入详解

    前言 在实际项目的开发中,我们一般会经历项目的开发阶段.测试阶段和最终上线阶段,每一个阶段对于项目代码的要求可能都不尽相同,那么我们如何能够游刃有余的在不同阶段下使我们的项目呈现不同的效果,使用不同的功能呢? 这里就需要引入环境的概念.官方文档中模式和环境变量说明 一般一个项目都会有以下 3 种环境: 开发环境(开发阶段,本地开发版本,一般会使用一些调试工具或额外的辅助功能): 测试环境(测试阶段,上线前版本,除了一些 bug 的修复,基本不会和上线版本有很大差别): 生产环境(上线阶段,正式对

随机推荐