当前位置: 首页 > news >正文

浅谈C++ const

引入

分别考虑以下代码:

#include <bits/stdc++.h>int main() {const int a = 1;const_cast<int &>(a) = 42;std::printf("%d\n", a);
}
#include <bits/stdc++.h>int main() {const int a = std::rand();const_cast<int &>(a) = 42;std::printf("%d\n", a);
}

请问两次代码分别会输出什么?

运行后不难发现,前者会输出42,后者则输出1。事实上,两者逻辑几乎一致:

  1. 定义一个常量a并初始化
  2. 强制修改a的值
  3. 输出a

那为什么行为上存在差异呢?

汇编分析

我们通过Compiler Explorer查看二者的汇编代码(省略部分代码),如下:

main:; ...lea     rdi, [rip + .L.str] ; 传递printf第一个参数mov     esi, 1 ; 传递printf的第二个参数xor     eax, eax  ; 将eax寄存器清零,便于printf调用call    printf ; 调用printf; 以上代码相当于printf("%d\n", 1);; ....L.str:.asciz  "%d\n"
main:; ...call    rand ; 调用rand函数lea     rdi, [rip + .L.str] ; 传递printf第一个参数mov     esi, 42 ; 传递printf的第二个参数xor     eax, eax ; 将eax寄存器清零,便于printf调用call    printf ; 调用printf; 以上代码相当于printf("%d\n, 42);; ....L.str:.asciz  "%d\n" ; 定义格式化字符串

观察到,编译器忽略了a的内存分配,并直接使用Magic Number作为A的值。

符号表替换

我们先分析代码A。通过查阅资料可知,编译器会进行符号表替换优化,具体来说,会将所有编译期常量替换为Magic Number,如以下代码:

const int a = 114514;
int b[a];

会被优化为

int b[114514];

这一优化发生在AST阶段,位于预处理之后,汇编之前。那么回到刚才的代码,

#include <bits/stdc++.h>int main() {const int a = 1;const_cast<int &>(a) = 42;std::printf("%d\n", a);
}

显然,按照刚才的逻辑,程序会被优化成这样:

#include <bits/stdc++.h>int main() {const int a = 1;const_cast<int &>(a) = 42;std::printf("%d\n", 1);
}

那么,此时显然

const int a = 1;
const_cast<int &>(a) = 42;

已经没有任何意义,那么编译器会根据as-if原则(编译器可以自由地改变程序,只要可观察行为与原始程序一致。,这段代码就会被优化。

最终被优化为:

#include <bits/stdc++.h>int main() {std::printf("%d\n", 1);
}

代码B分析

接下来考虑代码B。我们知道,符号表替换适用于编译期常量,显然对于代码B不适用。接下来,编译器会考虑将变量a分配至.rodata段(只读数据段)。然而很不幸,上述方法不适用于局部变量。因此,编译器只能像普通变量一样处理a,只不过在编译器进行检查。

但是,const_cast会拒绝编译器检查,相当于告诉编译器“我保证这段代码是安全的”。于是,编译器检查通过后,源代码

#include <bits/stdc++.h>int main() {const int a = std::rand();const_cast<int &>(a) = 42;std::printf("%d\n", a);
}

会被处理为像这样:

#include <bits/stdc++.h>int main() {int a = std::rand();a = 42;std::printf("%d\n", a);
}

此时,编译器会进行数据流分析最后发现:
唯一一次获取a的值,即printf时,a的值是确定的,为42。因此,编译器会认为a是没有意义的,优化成这样:

#include <bits/stdc++.h>int main() {std::rand();std::printf("%d\n", 42);
}

请注意,此处的std::rand是有副作用的,也就是说,执行std::rand会改变程序状态。因此,编译器不会删除std::rand的调用,但是会忽略其返回值。

写在最后

永远不要尝试修改一个常量!这在C++中是未定义行为,也就是说,这种操作的结果是不确定的,编译器可以对未定义行为进行任何处理。上述分析只是当前主流编译器的普遍优化方法。

参考

  1. ISO/IEC 14882:2024
  2. https://godbolt.org/
  3. https://chat.deepseek.com/
http://www.sczhlp.com/news/9801/

相关文章:

  • NextJS 02 - 服务端渲染
  • Supervisor安装与使用
  • 深入解析:【JavaEE】多线程之Thread类(下)
  • proxmox云镜像安装过程
  • 为什么Moka能留住核心人才?智能继任计划+离职风险预测
  • 文件访问被拒绝。
  • ArcgisPro ArcPy (还未)实现缩放至图层
  • Linux环境 RocketMQ 5.X 三主三从集群部署
  • 从嘉手札2025-8-11
  • android开发将项目升级到target35的解决方法
  • 常见光照范围
  • 无监督训练在NLP中的价值体现
  • HFSS许可证多用户支持
  • 【斯普林格出版、快至见刊后1个月检索】第五届现代教育技术与社会科学国际学术会议(ICMETSS 2025)
  • 统计出哪个时间段在线人数最多
  • 8.11
  • 背景图
  • 哨兵卫星 在线查看网站
  • ExpeRepair: Dual-Memory Enhanced LLM-based Repository-Level Program Repair 论文笔记
  • GPT5模型工程重构实践
  • rdx与edx之间的关系
  • SSRF靶场
  • ubuntu上Docker的安装与卸载
  • C++编程2025秋课堂教学
  • 防止NLP模型更新中的性能回退技术解析
  • 1431. 拥有最多糖果的孩子
  • 35页PPT|零售行业自助数据分析方法论:指标体系构建平台集成、会员与商品精细化运营实践
  • 题解:P13685 【MX-X16-T3】「DLESS-3」XOR and Impossible Problem
  • 题解:P13684 【MX-X16-T2】「DLESS-3」XOR and Multiply
  • 有没有哪个勇士能顶顶百度的网盘,限速的太恶心了