C++ / a working model

10 / 80   ·   C++11   ·   约 8 分钟

宏与 inline:文本替换不等于函数调用

先记住这句话

函数式宏在预处理阶段替换记号,不提供参数类型、正常作用域或单次求值保证。inline 函数具有完整函数语义,主要关联多翻译单元定义规则,并不强制机器码内联;常量和计算通常优先使用 constexpr 或模板。

本篇内容
  1. 展开之后才有表达式
  2. 函数保留类型和调用边界
  3. inline 的重要承诺是定义规则
  4. 运行示例
  5. 动手练习

展开之后才有表达式

#define BAD_DOUBLE(x) x + x 接收的是预处理记号。3 * BAD_DOUBLE(1 + 2) 展开成 3 * 1 + 2 + 1 + 2,结果是 8 而非期望的 18。给参数和整体加括号能修正这个优先级问题,却不能解决重复求值。

例如把参数出现两次的宏用于自增表达式,可能导致副作用次数变化甚至未定义行为,因此示例只用无副作用字面量展示错误。宏也不遵守 C++ 命名空间作用域;碰巧同名的成员或库函数记号都可能被预处理器替换。

函数保留类型和调用边界

普通函数或函数模板按语言规则接收参数,类型检查和重载决议在调用处发生。示例中的倍增函数按值接收一个 int,调用 twice(++n) 时自增只发生一次,函数体复用的是参数值,而不是重复粘贴调用者的表达式。

constexpr 允许满足条件的调用参与常量求值,并不要求每次调用都发生在编译期。它适合替换表达式常量和小型纯计算宏;模板适合保留类型泛化。条件编译、字符串化和记号拼接则仍是预处理器擅长的不同任务。

inline 的重要承诺是定义规则

inline 不是“强制消除调用开销”。编译器可以不内联一个 inline 函数,也可以内联一个没写 inline 的函数。对于通常的头文件代码,inline 允许符合单一定义规则的同一函数定义出现在多个翻译单元中,同时仍表示同一个实体。

多份定义需要满足 ODR 条件,不能让不同源文件用不同宏把函数体改成不同实现。constexpr 函数隐含 inline;普通头文件场景中类内定义的成员函数也隐含 inline,命名模块另有规则。把性能问题归结为有没有写这个关键字,通常会忽略真正的优化上下文。

容易答错的地方

  • 宏参数整体加括号只能修复结合方式,不能保证参数只求值一次。
  • inline 不允许不同翻译单元提供互不一致的函数定义;这种 ODR 违规可能没有可靠诊断。

运行一个例子

最低标准 C++11 · 完整程序 · 下载 .cpp

#include <cassert>
#include <iostream>

#define BAD_DOUBLE(x) x + x

constexpr int twice(int value) { return value + value; }

int main() {
    const int expanded = 3 * BAD_DOUBLE(1 + 2);
    const int called = 3 * twice(1 + 2);
    int n = 2;
    const int one_evaluation = twice(++n);
    assert(expanded == 8);
    assert(called == 18);
    assert(n == 3 && one_evaluation == 6);
    std::cout << expanded << ' ' << called << ' ' << n << '\n';
}

#undef BAD_DOUBLE

在本地编译

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-macros-inline.cpp -o example && ./example

预期结果

8 18 3

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

把 #define SQUARE(x) ((x) * (x)) 替换为保持单次实参求值的 C++11 函数,并说明还剩什么算术前提。

查看参考答案

可写 constexpr int square(int x) { return x * x; }。square(++n) 会先把一次自增结果作为值传入,再使用该值两次。但 int 乘法仍可能有符号溢出;接口必须限制输入范围,或改用有检查的算法。替换宏消除了重复求值,不会自动消除数值溢出。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录