inline与nullptr-从宏的坑到符号表的真相

inline 与 nullptr:从宏的坑,到符号表的真相

我的github:(https://github.com/xcx55/ubuntu-linux-project)

感谢各位大佬参观我的github!!

C++ 入门的收尾两块:inline 和 nullptr。它们有个共同点------都是给 C 时代的老机制打补丁。inline 补的是宏函数,nullptr 补的是 NULL。而 inline 这块真正的看点,是把之前欠下的"符号表"账算清。

一、宏函数到底难写在哪

先看一个 ADD 宏的几种写法:

c 复制代码
//#define ADD(int a, int b) return a + b;   // 直接错,宏不是函数
//#define ADD(a, b) (a + b)                  // 少了外括号,有坑
#define ADD(a, b) ((a) + (b))               // 正确写法

宏不是函数,是预处理阶段的纯文本替换。想写对一个宏,括号一个都不能少:

  • 外括号 :防运算符优先级。ADD(1, 2) * 3 若展开成 1 + 2 * 3 就全错了,所以整体必须套括号;
  • 内括号 :防参数里的表达式。ADD(1 | 2, a & b) 展开时 + 优先级高于 | 和 &,参数不加括号照样错;
  • 甚至还有分号的坑:宏后面跟 ; 会展开成空语句。

宏的账面评价:优点是预处理阶段替换,不用建立栈帧,本质是一种提效;缺点是复杂、容易写错、没有类型安全检查、不能调试。为了一个加法要裹三层括号,这活没法干------C++ 的 inline 就是来接替它的。

二、inline:有条件的提效

inline 的思路跟宏一样:不建栈帧,直接在调用处展开。但它是有类型检查、能调试的真函数。

但不能无脑用。函数调用太多次、每次都原地展开,会有代价:

  1. 可执行程序变大------每处调用都复制一份代码;
  2. 展开太多反而效率降低。

所以 inline 的适用标准一句话:短小且频繁调用的函数,才用 inline。递归函数、长函数,编译器多半也不会真的展开它(inline 对编译器只是建议,不是命令)。

三、重头戏:inline 为什么不能声明与定义分离

这条规则是全篇最有味道的部分,它逼着你把之前欠的"符号表"账补上。

先回忆普通函数怎么跨文件工作的(预处理→编译→汇编→链接四步走):

复制代码
F.h  F.cpp  Test.cpp
  ↓ 预处理
F.i  Test.i          // 头文件展开
  ↓ 编译(检查语法、生成汇编代码)
F.s  Test.s
  ↓ 汇编(转二进制机器码)
F.o  Test.o          // 各自形成符号表
  ↓ 链接(合并符号表,找地址)
xxx.exe

普通函数的流程:汇编代码需要函数地址来 call。本文件里如果只有声明没有定义,编译器先留着(有声明才能去查表,没有声明它连表都不去查);等链接时所有 .o 的全局域合并成一张大符号表,去表里找到函数地址,call 过去。

inline 函数呢?它的特性是直接在调用处展开,压根不需要函数地址来 call------汇编指令里就没有 call 指令。所以:

inline 函数不会进入符号表。

于是声明定义分离这条路直接堵死:链接器拿着"找函数地址"这个任务去查表,inline 根本不在表里------找不到就是硬错误,连"未定义引用"的挣扎机会都没有。所以 inline 函数的定义必须跟声明绑在一起,通常直接写在头文件里。

顺带把 static 的账也算了:static 函数同样不进符号表(编译期就不让它进小表,链接时自然不进大表),所以其他文件查表也找不到它------这就是头文件里放 static 函数定义不会重定义的原因。三个角色一张表看清:

角色 需要函数地址 call 吗 进符号表吗 声明定义分离
普通函数 需要 进 可以
inline 函数 不需要(编译期原地展开) 不进 不行,找不到就是错
static 函数 需要 不进 可以,但仅限本文件

四、nullptr:NULL 在 C++ 里水土不服

NULL 不是关键字,是个宏,stddef.h 里的真身:

c 复制代码
#ifndef NULL
    #ifdef __cplusplus
        #define NULL  0            // C++ 里 NULL 就是 0
    #else
        #define NULL  ((void *)0)  // C 里 NULL 是空指针
    #endif
#endif

为什么 C++ 不肯用 (void*)0?因为 C++ 的类型检查比 C 严:C 语言允许 void* 隐式转换成任意指针类型,C++ 不允许。那 NULL 在 C++ 里只能定义为 0------这就出事了:

cpp 复制代码
void f(int x)    { cout << "f(int)" << endl; }
void f(int* p)   { cout << "f(int*)" << endl; }

f(NULL);    // 调的是 f(int)!NULL 是 0,被当成整数
f(nullptr); // 调的是 f(int*)

NULL 本意是给空指针用的,结果在重载解析时被匹配成了 int------语义完全拧了。

C++11 引入 nullptr:它是一个特殊类型的字面量,只能被隐式转换为指针类型,不能转换为整数类型 。重载解析永远站在指针那边,f(nullptr) 稳稳落在 f(int*) 上。

一句话收尾:C++ 里给指针置空,写 nullptr,别再写 NULL------旧习惯该忘的就得忘。


inline 补宏的坑(类型检查 + 可调试),nullptr 补 NULL 的坑(类型精确)。两个补丁背后是同一件事:C++ 把类型系统收紧了。下一篇开始进真正的主场------类和对象,那是 C++ 语法层最大的范式转变。

相关推荐
gwf21618 小时前
RDMA事件与异步通知机制:CMA事件与设备事件深度解析
驱动开发·linux内核·rdma·rdma_cm·异构计算·异步事件·cve分析
万物智能信息科技21 小时前
温湿度传感器驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
人工智能·驱动开发·华为·开源·harmonyos·鸿蒙
硅基手札1 天前
【串口技术系列文档 04】UART协议深度解析
驱动开发·stm32·单片机·嵌入式硬件·mcu·计算机外设
kakakahahahaha1 天前
Windows 10禁用自动更新:gpedit、services.msc、计划任务与风险边界
windows·驱动开发·电脑·笔记本电脑·软件需求·windows系统更新
sukalot1 天前
Windows 驱动实例分析系列:injdrv 驱动分析 - src篇(三)
windows·驱动开发
2401_862880821 天前
i.MX6ULL 嵌入式 Linux 系统移植
驱动开发
sunoo-2292 天前
【嵌入式Linux驱动学习】Day1-Day2 全流程梳理:环境搭建→U-Boot→内核→根文件系统→驱动基础
linux·运维·驱动开发·笔记·学习·阿里云·恩智浦
u1301302 天前
GitHub 热榜项目:日榜(2026-10-07)
驱动开发·github
万物智能信息科技2 天前
血氧心跳传感器MAX30100芯片驱动开发—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
人工智能·驱动开发·华为·开源·harmonyos·鸿蒙