必须同时加-fprofile-arcs和-ftest-coverage,否则gcov无覆盖率数据;前者插桩计数,后者生成.gcno;仅对gcc/g++编译目标生效,需在cmake中显式配置。

gcc编译时必须加-fprofile-arcs -ftest-coverage
不加这两个flag,gcov压根看不到任何覆盖率数据——它不是运行时自动采集的,而是靠编译器在插桩(instrument)时埋点。漏掉任一flag都会导致生成的二进制里没覆盖率逻辑,后续所有步骤都白忙。
常见错误现象:gcov报告全是#####(表示无计数),或者lcov生成的HTML里所有行标灰、覆盖率为0。
-
-fprofile-arcs:让gcc在分支跳转处插入计数代码 -
-ftest-coverage:生成.gcno文件(含源码结构信息),供gcov解析 - 必须同时启用,且仅对用
gcc/g++编译的目标文件生效;CMake项目需在CMAKE_CXX_FLAGS或target_compile_options里显式追加 - 注意:链接时不用额外参数,但运行测试前得确保环境变量
GCOV_PREFIX(如需重定向输出路径)已设好
gcov只处理单个.o或.gcno文件,不能直接扫整个工程
gcov本质是按目标文件粒度工作的——给它一个.gcno和对应的.gcda,它才输出那个源文件的覆盖率。很多人误以为跑一次gcov *.gcno就能汇总,结果要么报错,要么只刷出部分文件。
使用场景:调试单个模块时快速验证;配合脚本批量处理多个.gcda生成原始.gcov文件。
立即学习“C++免费学习笔记(深入)”;
- 典型命令:
gcov path/to/file.gcno(自动找同目录file.gcda) - 若
.gcda不在源码目录,用-o指定路径:gcov -o build/objs/ src/foo.cpp.gcno - 输出的
foo.cpp.gcov是带标记的源码副本,-行未执行,#####行无计数,数字行是执行次数 - 别手动删
.gcda:它记录运行时计数,每次测试后要保留才能被lcov合并
lcov生成HTML前必须先lcov --capture收集数据
直接拿一堆.gcda扔给lcov -a会失败——lcov需要先通过--capture把当前工作目录下所有.gcda读进来,转换成中间格式(.info),再做过滤、合并、生成HTML。
性能影响:大工程里--capture可能卡几秒,因要遍历所有.gcda并解析对应.gcno;若.gcno路径不对(比如编译路径和当前路径不一致),会跳过该文件且静默失败。
- 基础流程:
lcov --capture --directory build/ --output-file coverage.base.info - 跑完测试后,再捕获一次:
lcov --capture --directory build/ --output-file coverage.test.info - 用
--diff或--remove过滤系统头、第三方库:lcov --remove coverage.test.info '/usr/*' '*/test/*' --output-file coverage.filtered.info - 最后
genhtml coverage.filtered.info --output-directory report/
C++模板、内联函数、异常路径容易漏覆盖
gcov对模板实例化体的覆盖率统计很脆弱——如果模板定义在头文件里且没被显式实例化,对应.gcda可能根本不存在;inline函数若被编译器内联掉,也不会生成独立计数;catch块若没触发异常,gcov就当它不存在。
容易踩的坑:看到HTML里某行标灰,第一反应是“代码没跑”,但更可能是编译器优化掉了该路径,或模板没实例化。
- 模板问题:在测试文件末尾加
template class MyTemplate<int>;</int>强制实例化,确保生成.gcno/.gcda - 禁用内联:编译时加
-fno-inline -fno-inline-small-functions(仅调试期用,别上生产) - 异常路径:写专门触发
throw的单元测试,或用__attribute__((noinline))标记catch块里的关键函数 - 注意
-O0不是万能解——某些路径在-O0下反而因缺少优化而无法触发(比如NRVO失效导致额外拷贝)










