## 内联函数 ## 判别规则(核心,唯一要记的) - **短小、频繁、要类型安全的简单逻辑 → 用 `static inline`;常量/位操作掩码 → 用宏;大函数、递归、被取地址 → 普通函数** - **关键:inline 只是"建议",编译器可能拒绝;宏参数可能多求值(副作用坑),内联不会** 一句话:**"想省调用开销又怕宏的坑 → 内联;纯常量掩码 → 宏;大而复杂 → 普通函数。"** ## 抽象描述(一句话本质) 内联函数 = 用 inline 修饰,编译器在调用处直接展开函数体(不跳转、不压栈)省调用开销。它是"宏的进化版":白拿了宏的省开销,还补了**类型检查**和**参数只求值一次**。但 inline 只是建议,编译器可拒绝;最大代价是代码膨胀。 ## 正反例(建立直觉) - **正例1**:`static inline int max(int a, int b){...}` → 调用处展开,无调用开销 - **正例2(副作用坑)**:`#define MAX(a,b) ((a)>(b)?(a):(b))` → `MAX(x++, y++)` 展开后 x/y **求值两次**,副作用重复;内联函数参数**只求值一次**,正常 - **正例3(类型检查)**:`MAX("abc", 3)` 宏能编过;`imax("abc", 3)` 内联报类型错误 - **正例4**:函数太大、递归、被取地址 → 编译器拒绝内联 - **反例1**:以为 inline 一定内联 → 错,只是建议,编译器最终决定 - **反例2**:以为宏能替代内联 → 宏无类型检查、参数可能多求值 ## 易混对比 | | 宏 | 内联函数 | 普通函数 | | -------- | ---------------- | ---------------------- | ------------ | | 时机 | 预处理文本替换 | 编译期展开(**建议**) | 编译期调用 | | 类型检查 | ❌ 无 | ✅ 有 | ✅ 有 | | 参数求值 | 可能**多次** | 一次 | 一次 | | 调用开销 | 无 | 无(若内联成功) | 有 | | 适合 | 常量、位操作掩码 | 短小频繁的真函数 | 大函数、递归 | ## 变体验证(3 题,全过=学会) 1. 陷阱题:`static inline int f(int x){ return x*x; }` 后 `int (*p)(int) = f;` → **不会内联**(取地址后必须保留函数实体,编译器拒绝内联该调用点) 2. 陷阱题:递归函数能内联吗?→ **不能**(内联 = 复制函数体,递归展开会无限复制自己,编译器放弃) 3. `#define SQUARE(x) ((x)*(x))` vs `square(x)` → `SQUARE(++n)` 对 n **求值两次**结果错;`square(++n)` **一次**结果对 ## 口述要点(面试怎么讲) - **结论先行**:短小频繁安全 → static inline;纯掩码常量 → 宏;大/递归/取地址 → 普通函数 - **为什么写 static inline 而不是 inline**:static 私有化之外,关键是**解决 inline 的链接麻烦**——C 里 `inline` 语义复杂(可能不生成独立符号或跨文件重复定义 → 链接报错);`static inline` = 每个文件各自一份,绝不产生链接冲突。所以嵌入式/内核默认 static inline - **编译器拒绝内联的清单**:函数太大(超阈值)、递归、被取地址、复杂循环/控制流、调用点太多(膨胀)、优化级别低(-O0 不内联) - **内联的代价**:**代码膨胀(code bloat)**——每个调用点复制一份函数体 → Flash 变大、I-cache 压力大。函数小且调用点少 → 赚;函数大或调用点极多 → 亏。内联后断点定位难、编译时间变长 - **宏高手的 typeof 技巧**:`#define min(x,y) ({ typeof(x) _x=(x); typeof(y) _y=(y); _x<_y?_x:_y; })` 能解决求值多次,但解决不了类型检查、调试、错误信息问题——Linux 内核仍大量用 static inline,编码风格明确"inline 优先于函数式宏" - **易错点**:以为 inline 一定内联;以为宏能完全替代内联;忘了内联的膨胀代价 ## 关系网络 | 相邻概念 | 和本主题的关系 | 孤立理解会犯的错 | | ----------------- | -------------------------------------- | ---------------------------------------------- | | define vs typedef | 宏 vs 内联:内联补了类型检查、求值一次 | 以为内联=宏换个写法 | | 函数指针 | 被取地址 → 编译器拒绝内联 | 以为 inline 函数不能取地址(能取,只是不内联) | | 编译过程 | 内联发生在编译优化阶段 | 以为内联是预处理/链接干的事 | | 性能优化 | 省调用开销 vs 代码膨胀 | 只看到省开销,忘了 Flash/缓存代价 | ## 学习日期 2026-08-03 --- _每主题一页,复习时只翻本目录。_