|
| 1 | +# 嵌入式现代 C++教程——constexpr:把计算推到编译期 |
| 2 | + |
| 3 | +在嵌入式开发中,我们习惯于用“时间”和“空间”来衡量一段代码的好坏。函数调用要不要内联,循环能不能展开,RAM 和 Flash 有没有被浪费。这些问题都很直观,也很工程。 |
| 4 | + |
| 5 | +constexpr这个是一个真正的好东西——**它不优化运行时,而是干脆让运行时什么都不用做。**这正是 `constexpr` 在嵌入式现代 C++ 中最有价值、也最容易被低估的地方。 |
| 6 | + |
| 7 | +------ |
| 8 | + |
| 9 | +## constexpr 解决的不是“快不快”,而是“还需不需要算” |
| 10 | + |
| 11 | +很多人第一次接触 `constexpr`,是从这种例子开始的: |
| 12 | + |
| 13 | +```cpp |
| 14 | +constexpr int square(int x) |
| 15 | +{ |
| 16 | + return x * x; |
| 17 | +} |
| 18 | +``` |
| 19 | +
|
| 20 | +然后编译器把 `square(8)` 直接替换成 `64`,看起来很神奇。 |
| 21 | +
|
| 22 | +但如果只停留在这个层面,很容易把 `constexpr` 理解成一种“更激进的 inline”。这是一个常见但并不准确的认识。 |
| 23 | +
|
| 24 | +`inline` 的核心问题是:**要不要在运行时展开函数体**。而 `constexpr` 关注的是:**这个计算是否还有必要存在于运行时**。一旦你意识到这个差异,就会发现它们解决的是完全不同的问题。 |
| 25 | +
|
| 26 | +------ |
| 27 | +
|
| 28 | +## 在嵌入式里,运行时并不是免费的 |
| 29 | +
|
| 30 | +在 PC 上,多一次整数乘法、多一次分支判断,几乎可以忽略不计。但在嵌入式系统中,运行时往往意味着更多的东西——可能是 Flash 中的指令读取,可能是 Cache miss,可能是启动阶段的额外执行路径,甚至可能影响到中断响应的确定性。很多嵌入式代码中都存在这样一种“无意识的运行时计算”:常量明明在编译期就已经确定,却依然被写成了运行时表达式。 |
| 31 | +
|
| 32 | +例如外设寄存器位掩码、数组大小、查表索引、协议字段偏移。这些值在系统启动后永远不会改变,但却被一次次地计算、判断、组合。`constexpr` 的出现,正是为了让这些“本不该存在于运行时的计算”,彻底消失。 |
| 33 | +
|
| 34 | +------ |
| 35 | +
|
| 36 | +## constexpr 的本质,是一种“更强的常量语义” |
| 37 | +
|
| 38 | +在嵌入式 C 时代,我们已经非常熟悉 `#define` 和 `enum`。它们同样可以在编译期得到结果,也同样不会引入运行时开销。 |
| 39 | +
|
| 40 | +但它们的问题在于:**缺乏类型、缺乏作用域、缺乏表达能力**。 |
| 41 | +
|
| 42 | +`constexpr` 并不是为了取代它们,而是提供了一种更现代、更安全的方式,把“这是一个编译期就能确定的事实”明确地告诉编译器。当你把一个函数、一个构造函数、一个变量声明为 `constexpr`,你其实是在表达一种强约束:**如果它不能在编译期完成,那它就不该存在。**这种约束一旦成立,编译器就会毫不犹豫地把结果直接写进目标文件。 |
| 43 | +
|
| 44 | +------ |
| 45 | +
|
| 46 | +## 嵌入式中,constexpr 最自然的落脚点 |
| 47 | +
|
| 48 | +在真实工程中,`constexpr` 很少是“炫技式”的。它更多是悄悄地改善系统结构。 |
| 49 | +
|
| 50 | +- 例如寄存器地址和位定义。过去我们用宏,现在我们可以用 `constexpr` 常量,让类型系统参与进来。 |
| 51 | +- 例如静态查找表的生成,编译期完成初始化,运行时只做索引。 |
| 52 | +- 例如协议相关的固定字段、偏移量、长度计算,在链接前就已经完全确定。 |
| 53 | +
|
| 54 | +这些地方使用 `constexpr`,几乎不会改变你代码的阅读方式,却能实实在在地减少运行时代码。 |
| 55 | +
|
| 56 | +------ |
| 57 | +
|
| 58 | +## constexpr 和嵌入式启动阶段 |
| 59 | +
|
| 60 | +在很多嵌入式系统中,启动阶段是一个被高度关注、但又容易被忽略细节的地方。初始化代码往往集中、复杂,而且执行一次就决定了系统之后的状态。如果这些初始化中包含大量本可以在编译期完成的计算,那么启动时间、代码体积和可验证性都会受到影响。`constexpr` 在这里的意义,并不只是“快一点”,而是**让启动路径更简单、更确定**。当某些值已经被固化进二进制文件时,你甚至不需要再关心它们是怎么被算出来的。 |
| 61 | +
|
| 62 | +------ |
| 63 | +
|
| 64 | +## constexpr 并不是“写了就一定发生” |
| 65 | +
|
| 66 | +和 inline 类似,`constexpr` 也经常被误解为一种“强制指令”。事实上,它更像是一种契约。 |
| 67 | +
|
| 68 | +你告诉编译器:这段逻辑**应该**可以在编译期完成。如果做不到,编译器要么拒绝编译,要么退化为运行时求值(取决于上下文)。这反而是一件好事。在嵌入式开发中,编译期失败往往比运行时问题要友好得多。而且越高版本的C++,越能支持更多行为的编译期计算。 |
| 69 | +
|
| 70 | +
|
| 71 | +
|
0 commit comments