cppcheck的assignmentinassert检查器专用于检测assert语句中赋值(=、+=等)、自增/自减(++/--)等副作用操作,因其在release下被移除会导致debug与release行为不一致,需显式启用--enable=warning才能触发警告。

直接看 assert 里有没有赋值、++、+= 这类操作——这是 C++ 中最隐蔽也最高频的“调用后数据变”源头,且 Debug 和 Release 行为不一致。
assert 语句里偷偷改了变量值
Cppcheck 的 assignmentInAssert 检查器专抓这事,但默认不启用。它会报类似 Assert statement modifies 'x' 的警告。
- 触发代码示例:
assert((x = 42) == 42);或assert(y++ > 0); - 问题本质:
assert在 Release 下被完全移除(NDEBUG宏定义时),赋值/自增操作随之消失,导致 Debug 正常、Release 异常 - 必须显式开启:
cppcheck --enable=warning your_file.cpp,否则这个检查器根本不会运行 - 别信“我只是读一下”,只要表达式里有
=、+=、++、--,就属于副作用,就危险
函数参数传递时发生隐式修改
传引用或指针时,函数内部可能没声明意图却改了原始变量,尤其当函数名看起来“只读”时(比如 validate()、inspect())。
- 检查函数签名:
void validate(Data& d)比void validate(const Data& d)更可疑 - 注意 const 成员函数里调用非 const 成员函数(通过
const_cast或 mutable 字段绕过) - 排查 lambda 捕获:[&] 捕获后在回调中修改了外部变量,而调用点离得很远
- 用编译器警告辅助:
-Wshadow防止局部变量遮蔽成员变量,-Wdangling-gsl(如果用 GSL)提示悬垂引用
宏展开引入不可见的副作用
宏不是函数,不遵守作用域和求值顺序,++、= 在宏体内展开后可能被多次执行。
- 典型陷阱:
#define MAX(a, b) ((a) > (b) ? (a) : (b)),调用MAX(i++, j++)会导致 i/j 自增次数不确定 - 检查所有带参数的宏定义,尤其是用于断言、日志、调试打印的宏
- 用
clang -E或gcc -E预处理源码,肉眼确认宏展开后是否含副作用表达式 - 能用
constexpr函数或内联函数替代的宏,优先替换
多线程下无锁访问共享变量
单线程看着没问题,一上多线程就“数据变了”,往往不是函数逻辑错,而是读写没同步。
- 即使函数本身只读,若另一线程正在写同一变量,TSan 会报告 data race —— 不是“读错了”,是“读写之间无顺序保证”
- 快速验证:
g++ -fsanitize=thread -fno-omit-frame-pointer -g your_file.cpp,跑一次就能暴露竞争点 - 特别注意信号处理函数、异步回调、定时器触发的函数,它们容易漏加锁
-
std::atomic只解决原子性,不解决业务逻辑一致性;std::mutex要包住整个临界区,不能只锁写不锁读
副作用最麻烦的地方在于:它不报错、不崩溃、甚至测试通过,只在特定构建模式或并发路径下悄悄改数据。盯紧 assert、宏、引用参数和线程上下文,这四类地方覆盖了绝大多数“调用后数据变”的真实场景。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











