静态分析基础:什么是静态分析?与动态测试的区别

各位同学,咱们今天聊点实在的。静态分析这个词,听起来挺唬人,对吧?

我刚开始接触嵌入式开发那会儿,也觉得这玩意儿是给那些写编译器的大神准备的。后来踩了几个坑,才明白——静态分析其实就是给代码做一次「体检」,不用跑程序,光看代码本身就能发现问题。

静态分析到底是什么?

说白了,静态分析就是在不执行程序的情况下,对源代码进行检查。你想想看,就像医生看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遍历逻辑。

小结一下

咱们今天聊了三件事:

  1. 静态分析 — 不跑代码,只看代码本身。和动态测试互补,不是替代。
  2. 词法分析 — 把代码拆成token,相当于给代码做分词。
  3. 语法分析 + AST — 检查语法是否正确,构建出代码的骨架结构。

下一章,我会带你看看这些基础概念在CI流水线里是怎么落地的。到时候咱们会聊到:怎么把静态分析工具集成到GitHub Actions或者Jenkins里,让每次提交代码都自动跑一遍检查。

嗯,今天就到这儿。有问题随时问我。