[[likely]]和[[unlikely]]是C++20标准的编译器优化提示,用于指导分支预测优化,不改变程序逻辑,仅在启用优化(如-O2)时生效,须置于if/else if/switch语句块开头,不可嵌套或用于表达式。
![c++ 属性化编程 c++20的[[likely]]和[[unlikely]]如何使用](https://img.php.cn/upload/article/001/221/864/177382083130900.jpg)
[[likely]] 和 [[unlikely]] 是编译器提示,不是运行时控制
它们不改变程序逻辑,只建议编译器对分支做不同优化:把 [[likely]] 标记的路径按“大概率走”生成更紧凑、更利于流水线的机器码;[[unlikely]] 则相反,常用于错误处理、边界检查等小概率路径。主流编译器(GCC 12+、Clang 13+、MSVC 19.30+)支持,但若编译器忽略或未启用优化(如 -O0),这些属性完全无效。
常见错误现象:[[likely]] 加在永远不执行的分支上,结果性能反而变差——因为编译器信了你,把冷代码塞进热路径的指令缓存区;或者在调试构建中反复验证行为,却忘了它本就不该影响逻辑。
- 只用在
if、else if、switch的语句块开头,不能标在表达式或函数声明上 - 不要嵌套使用,比如
if (x) [[likely]] { if (y) [[unlikely]] { ... } }—— 外层提示已覆盖上下文,内层易被忽略 - 配合
-O2或更高优化等级使用,-Og下部分编译器可能不生效
实际写法:紧跟在 if/else if/switch 后,同一行或换行都可
语法宽松,但位置敏感:必须直接修饰目标语句块,不能隔空或插在条件表达式中间。C++20 标准只要求它出现在语句前,不强制绑定到某个 token。
典型场景:系统调用返回值检查、无锁队列的空/满判断、配置开关的默认分支。
立即学习“C++免费学习笔记(深入)”;
if (ptr != nullptr) [[likely]] {
use(ptr);
} else [[unlikely]] {
handle_null();
}
switch (status) {
case OK: [[likely]] {
commit();
break;
}
case TIMEOUT: [[unlikely]] {
retry();
break;
}
}
-
[[likely]]和[[unlikely]]是独立属性,不是成对关键字,不必一一对应 - 不能写成
if [[likely]] (x) { ... }—— 属性必须在if后、左大括号前 - 宏封装需谨慎:比如
#define LIKELY [[likely]]可用,但#define IF_LIKELY(x) if (x) [[likely]]易引发宏展开歧义
和传统分支预测提示(如 __builtin_expect)比有什么区别
__builtin_expect 是 GCC/Clang 特有内建函数,要改写条件表达式:if (__builtin_expect(ptr != nullptr, 1));而 [[likely]] 更干净,不污染逻辑表达式,也更易读。但兼容性差:老项目用 __builtin_expect 还得保留,跨平台时还得加宏适配。
性能影响取决于后端优化质量。实测显示,在热点循环中,[[likely]] 对 GCC 13 的分支折叠(branch folding)和跳转预测 hint 有明显作用;但 Clang 14 在某些间接跳转场景下仍更依赖 __builtin_expect。
- 已有
__builtin_expect的代码,不急着全换,尤其涉及内联汇编或手写 asm 模板时 - 新项目优先用
[[likely]],但别假设它能替代 profile-guided optimization(PGO) - Windows + MSVC 下,
[[likely]]支持较晚(19.30 起),且对 x86/x64 行为一致,ARM64 上效果尚未广泛验证
容易被忽略的细节:作用域、模板和 constexpr 环境
属性只作用于紧邻的语句块,不穿透作用域。在模板中可用,但实例化时才由具体类型决定是否触发优化;constexpr 函数里允许写,但编译期求值阶段这些提示不生效——只有生成的运行时代码才可能被优化。
一个隐蔽坑:lambda 表达式内部用 [[likely]],若 lambda 被捕获并延迟执行(比如塞进 std::function),提示依然只影响 lambda 定义处的分支,不影响调用时机的运行时预测。
- 类成员函数内、构造函数初始化列表里不能用——那里没有语句块可修饰
- constexpr if 不支持
[[likely]],因为它是编译期分支,不生成运行时跳转 - 宏定义中混用
[[likely]]和#ifdef时,注意预处理器不识别属性,可能意外删掉整行










