静态分析基础:什么是静态分析?与动态测试的区别
各位同学,咱们今天聊点实在的。静态分析这个词,听起来挺唬人,对吧?
我刚开始接触嵌入式开发那会儿,也觉得这玩意儿是给那些写编译器的大神准备的。后来踩了几个坑,才明白——静态分析其实就是给代码做一次「体检」,不用跑程序,光看代码本身就能发现问题。
静态分析到底是什么?
说白了,静态分析就是在不执行程序的情况下,对源代码进行检查。你想想看,就像医生看X光片,不用切开你的身体,就能知道骨头有没有裂。
静态分析工具会扫描你的每一行代码,寻找潜在的问题。比如:
- 变量声明了但没用
- 数组可能越界
- 空指针解引用
- 死代码(永远不会执行的代码)
- 违反编码规范的地方
我在项目中遇到过最典型的一个例子:有个同事写了一段看起来很完美的代码,逻辑也没问题。但静态分析工具报了一个警告——某个局部变量在赋值之前就被读取了。嗯,这玩意儿在大部分情况下不会出问题,但一旦编译器优化开了某个级别,行为就完全不可控了。那次排查花了我整整两天。
核心要点:静态分析关注的是代码的「结构」和「规则」,而不是「行为」。
静态分析 vs 动态测试
很多新手会问:我跑一遍单元测试不就行了吗?为什么还要做静态分析?
这两者其实是互补的,不是替代关系。我画个表格给你看:
| 对比维度 | 静态分析 | 动态测试 |
|---|---|---|
| 执行代码 | 不需要 | 需要 |
| 发现时机 | 编码阶段 | 测试阶段 |
| 覆盖范围 | 所有代码路径 | 仅测试用例覆盖的路径 |
| 典型问题 | 语法错误、编码规范、潜在bug | 功能错误、性能问题、内存泄漏 |
| 误报率 | 较高(需要人工判断) | 较低(实际运行结果) |
| 执行速度 | 快(秒级到分钟级) | 慢(取决于测试用例数量) |
为什么会这样?我举个例子你就明白了。
假设你写了一个函数,里面有个if-else分支。动态测试只测试了if分支,else分支从来没被执行过。但静态分析会告诉你:else分支里有个空指针操作。这就是静态分析的价值——它能发现那些「你没想到要测」的路径。
我的建议:静态分析应该跑在动态测试之前。先让静态分析扫一遍,把明显的低级错误干掉,再去做动态测试。这样效率最高。
词法分析:把代码拆成「单词」
好,现在咱们深入一点。静态分析工具是怎么工作的?
第一步叫词法分析。说白了,就是把你的源代码拆成一个个最小的单元——token(令牌)。
比如这段代码:
int a = 10 + b;
词法分析器会把它拆成:
int— 关键字a— 标识符=— 运算符10— 字面量+— 运算符b— 标识符;— 分隔符
每个token都带着类型信息和位置信息(在哪一行、哪一列)。
我记得刚开始学编译原理时,觉得这步可有可无。直到有一次,我在一个嵌入式项目里写了一个宏,展开后有个分号放错了位置。词法分析器直接报了个「意外的token」错误。要是没有这步,编译都过不了,更别提后面的分析了。
语法分析:检查句子通不通
词法分析完了,接下来是语法分析。这一步检查的是:这些token组合在一起,是否符合语言的语法规则。
你想想看,中文里「我吃饭」是通顺的,「饭吃我」就不对。代码也一样。
语法分析器会根据语言的文法规则,构建一棵语法树。比如:
if (x > 0) {
y = 1;
}
语法分析器会识别出:这是一个if语句,条件表达式是 x > 0,执行体是 y = 1;。
如果写成:
if (x > 0 {
y = 1;
}
少了右括号,语法分析器就会报错。这就是语法分析干的事。
注意:语法分析只能检查「语法是否正确」,不能检查「逻辑是否正确」。比如 if (x = 0) 在C语言里是合法的语法(赋值表达式),但很可能不是你想要的(比较操作)。这种问题需要更高级的分析才能发现。
抽象语法树(AST):代码的骨架
语法分析的结果,就是一棵抽象语法树(AST)。
AST是什么?说白了,就是代码的骨架结构。它去掉了那些不重要的细节(比如分号、括号、空格),只保留代码的逻辑结构。
举个例子,对于表达式 a + b * 3,AST长这样:
+
/ \
a *
/ \
b 3
你看,这棵树清晰地表达了运算优先级:b * 3 先算,然后再加上 a。
AST在静态分析中特别重要。为什么?因为很多分析都是在AST上做的。比如:
- 检查变量作用域
- 检查类型是否匹配
- 检查控制流是否完整
- 检查是否有未使用的变量
我在做CI流水线集成时,就经常用AST来做自定义规则检查。比如我们团队规定:所有中断服务函数里不能调用 printf。我写了个小工具,遍历AST找到所有函数调用节点,如果发现中断函数里有 printf,就直接报错。这比人工code review靠谱多了。
避坑指南:我曾经在AST上吃过一次亏。当时我写了一个分析工具,直接操作AST节点。结果换了编译器版本后,AST的结构变了,工具直接崩了。所以我的建议是:尽量使用成熟的静态分析框架(比如Clang的LibTooling),不要自己手写AST遍历逻辑。
小结一下
咱们今天聊了三件事:
- 静态分析 — 不跑代码,只看代码本身。和动态测试互补,不是替代。
- 词法分析 — 把代码拆成token,相当于给代码做分词。
- 语法分析 + AST — 检查语法是否正确,构建出代码的骨架结构。
下一章,我会带你看看这些基础概念在CI流水线里是怎么落地的。到时候咱们会聊到:怎么把静态分析工具集成到GitHub Actions或者Jenkins里,让每次提交代码都自动跑一遍检查。
嗯,今天就到这儿。有问题随时问我。