文章目录
生成调用关系图
许多 IDE 都有生成函数调用关系图的功能, 例如, Visual Studio, Source Insight, 此外还有很多工具, 如Codeviz, cflow 等也可以生成函数调用图. Doxygen 也不例外, 它可以在 html 文档中展示函数, 文件, 结构体等的调用关系.
本文内容来自网络搜集, 官方手册以及我自己的尝试. 在网上浏览一圈之后, 我终于整明白了怎么用 doxygen 生成 C 函数的调用关系图了, 感谢以下参考资料提供的帮助:
安装 graphviz
doxygen 借助 graphviz 来绘制图形, 而 graphviz 是第三方的工具, 不属于 doxygen 的一部分, 所以我们首先要安装 graphviz.
进入官方的下载页面 Download | Graphviz, 选择一个版本下载. 为了避开安装程序, 这里下载的是 “ZIP archive” 格式的

下载完成之后, 将解压出的 bin 文件夹, 如 C:\...\bin 加入到环境变量 PATH. 命令行输入 dot -h, 没有报错就算安装成功了.
生成函数调用关系图(callgraph)
通过 CALL_GRAPH = YES 生成函数调用关系图
要让 doxygen 生成函数的调用关系图很简单, 只要在配置里设置 HAVE_DOT = YES 和 CALL_GRAPH = YES 即可.
小栗子
我们创建一个文件夹, 命名为 hello_dox_callgraph, 在文件夹内创建文件 lib.c; 命令行进入该目录, 输入doxygen -g 生成配置模板. 此时的目录结构应该是这样的:
|--hello_dox_callgraph
|--lib.c
|--Doxyfile
在 lib.c 输入如下代码:
/** @file */
/** @brief a function be called */
void funcA() {}
/** @details calls funcA(). */
void callFuncA(){
funcA();
}
/** @details place holder */
void callFuncACallCallFuncA() {
funcA();
callFuncA();
}
命令行输入 doxygen Doxyfile , 输出文档. 最后用浏览器打开 html\index.html, 选择 Files -> lib.c, 就可以看到下面的图形了:

深入细节
我们先来看官方手册中, tag CALL_GRAPH 的说明:
If the CALL_GRAPH tag is set to YES then doxygen will generate a call dependency graph for every global function or class method. Note that enabling this option will significantly increase the time of a run. So in most cases it will be better to enable call graphs for selected functions only using the \callgraph command. Disabling a call graph can be accomplished by means of the command \hidecallgraph.
The default value is: NO.
This tag requires that the tag HAVE_DOT is set to YES.
这段文字告诉我们, 如果 CALL_GRAPH 被设置为 YES, 生成调用图的对象是所有全局函数, 所以会消耗大量时间, 因此建议用 @callgraph 命令, 只给真正需要关系调用图的函数生成图形.
此外, 在研究的时候我还发现, 生成调用图的函数, 必须要有 detail, 也就是用 @details 命令指出的内容.

如上图所示, callFuncA() 使用 @details 指出的内容会在 Function Documentation 下方的位置展示, 生成的调用图也被放在这里. 而 funcA() 只用到 @brief , 所以没有在下面展示. 因此, 虽然一个函数生成了调用图, 但由于这个函数没有 detail, 调用图自然就无处可放, 也就不会显示出来啦 ╮(╯▽╰)╭
总结一下, 在本节中, 要生成调用图, 需要以下几个条件:
HAVE_DOT = YES;CALL_GRAPH = YES;- 要为一个函数生成调用关系图, 这个函数必须要有 detail 部分.
通过@callgraph 命令生成函数调用关系图
前面提到可以用 @callgraph 命令为函数按需生成调用图. 先来看官方手册的说明:
When this command is put in a comment block of a function or method and HAVE_DOT is set to YES, then doxygen will generate a call graph for that function (provided the implementation of the function or method calls other documented functions). The call graph will be generated regardless of the value of CALL_GRAPH.
Note
The completeness (and correctness) of the call graph depends on the doxygen code parser which is not perfect.
该命令的用法很简单, 放在函数的 special comment block 里就行了, 但是要求该函数调用的所有函数也要被注释(documented), 否则就不会显示该函数的调用关系图. 这要求确实很严苛, 特别是在想看老项目里比较复杂的函数调用关系的时候, 就只能在配置里设置 EXTRACT_ALL = YES 来解决这个问题了.
麻烦的是, 运用这个命令还有很多其他的小坑, 将通过栗子(们)展示.
栗子1 - 简单加上 @callgraph
首先把上一个例子中的配置文件 Doxyfile, 和输出 html\, latex\ 删除. 命令行输入 doxygen -g 重新生成配置, 然后完成必要的设置 HAVE_DOT = YES.
我们在 callFuncACallCallFuncA() 函数的注释里加上这条 @callgraph 命令:
/** @callgraph
@details place holder */
void callFuncACallCallFuncA() {
funcA();
callFuncA();
}
运行 doxygen Doxyfile, 生成的图形是这样的:

点解呢? callFuncACallCallFuncA() 的调用关系怎么只有一层? 倒是 callFuncA() 和预料的一样, 没有图形.
栗子2 - 给被调用的函数也加上 @callgraph
我们试着给 callFuncA() 也加上 @callgraph 命令:
/** @callgraph
@details calls funcA(). */
void callFuncA(){
funcA();
}
命令行运行 doxygen Doxyfile, 刷新网页再看, callFuncA() 有了图形, callFuncACallCallFuncA() 的图形也变成完整的两层了:

个人猜测这种现象的原因是, 栗子1 中, doxygen 只从callFuncACallCallFuncA() 的函数体中了解到它调用了 FuncA() 和 callFuncA(), 所以调用关系图只显示了一层. 而在栗子2 中, doxygen 在为 callFuncA() 生成调用关系图的时候, 了解到它也调用了 funcA(), 所以在callFuncACallCallFuncA() 的图中, 就把所有已知的关系整合到一起了.
栗子3 - 更自动的解决方法
在前面的例子中, 我们发现, 要想生成完整的调用关系图, 我们就得给每个被调用的函数的注释都写上 @callgraph 命令. 这种做法的弊端很明显: 一来是很麻烦, 对于不使用 doxygen 的老项目来说甚至是不可能的; 二来是违背了"按需画图"的初衷. 还有一个办法可以解决这个问题, 那就是在配置文件中, 令 REFERENCES_RELATION = YES.
我们先来看看官方手册中, 这个 tag 的说明:
If the REFERENCES_RELATION tag is set to YES then for each documented function all documented entities called/used by that function will be listed.
The default value is: NO.
将这个 tag 设为 YES, 则会在函数的文档中, 把该函数调用的所有已注释(documented)的函数都列出来.
我们将其设为 YES, 然后只对 callFuncACallCallFuncA 使用命令 @callgraph:
/** @file */
/** @brief a function be called */
void funcA() {}
/** @details calls funcA(). */
void callFuncA(){
funcA();
}
/** @callgraph
@details place holder */
void callFuncACallCallFuncA() {
funcA();
callFuncA();
}
命令行 doxygen Doxyfile 输出, 刷新网页:

可以看到, callFuncACallCallFuncA() 的调用关系图变完整了. 同时文档中还多了一行: References callFuncA(), and funcA()., 这就是 tag REFERENCES_RELATION 本来的作用. 个人猜测, 这个 tag 强制 doxygen 分析了所有函数的调用关系, 而 doxygen 分析出所有函数调用关系的副作用就是, 生成函数关系调用图的时候也完整了.
栗子4 - 被调用的函数没有被注释(documented)
官方手册提到, @callgraph 作用的函数, 其调用的函数必须已被注释. 那如果没有被注释会怎样呢? 在上面例子的基础上, 我们试着去掉 funcA() 的注释:
/** @file */
void funcA() {}
/** @details calls funcA(). */
void callFuncA(){
funcA();
}
/** @callgraph
@details place holder */
void callFuncACallCallFuncA() {
funcA();
callFuncA();
}
输出, 刷新网页:

好家伙, 调用关系图直接不见了. 注释掉 callFuncACallCallFuncA() 对 funcA() 的调用试试:
/** @callgraph
@details place holder */
void callFuncACallCallFuncA() {
// funcA();
callFuncA();
}
输出, 刷新网页:

调用关系图出来了. 这感觉是, 函数体内所有被调用的函数都必须被注释吗? 这样看来, EXTRACT_ALL = YES 真是保证生成函数调用关系图的必要辅助选项啊.
总结
利用 @callgraph 可以按需生成函数调用关系图, 但是有一些要求, 也有一些小坑:
HAVE_DOT = YES;- 所有被调用的函数都要被注释(documented). 对于不能达到此要求的老项目, 可以设置
EXTRACT_ALL = YES; - 最好设置
REFERENCES_RELATION = YES, 以确保调用关系图的完整;
生成函数被调用图(callergraph)
上面介绍了如何生成函数关系调用图, doxygen 还提供了一个镜像功能: 生成被调用图, 展示一个函数被哪些函数调用了. 对应关系如下:
| 调用关系 | 被调用关系 |
|---|---|
CALL_GRAPH | CALLER_GRAPH |
@callgraph | @callergraph |
REFERENCES_RELATION | REFERENCED_BY_RELATION |
栗子
callergraph 的套路和 callgraph 是一样的, 在上面例子的基础上给 funcA() 加上@callergraph 和 @details, 配置设置 REFERENCED_BY_RELATION = YES:
/** @file */
/** @callergraph
@details a simple function */
void funcA() {}
/** @details calls funcA(). */
void callFuncA(){
funcA();
}
/** @callgraph
@details place holder */
void callFuncACallCallFuncA() {
funcA();
callFuncA();
}
doxygen Doxyfile 输出, 刷新网页:

生成结构体和文件的调用关系图
除了函数调用关系, 我们还可以利用 doxygen 生成结构体和文件的调用关系, 而套路和生成函数关系调用图一模一样.
栗子 - 生成结构体的调用关系图
新建文件 struct.c , 输入以下代码:
/** @file */
/** @details a struct to be contained */
struct st_a {};
/** @details a struct contains other struct
@callgraph */
struct st_b {
struct st_a mem_a;
struct st_b mem_b;
};
配置文件设置 HAVE_DOT = YES, 命令行 doxygen Doxyfile 输出文档, 浏览器打开 html/index.html, 依次点击链接 Files -> struct.c -> st_b, 就可以看到下面的效果了:

也可以点击链接 Classes -> st_b 进入这个页面.
如果是纯 C 语言的工程, 还可以设置配置
OPTIMIZE_OUTPUT_FOR_C = YES, 这样文档中的名词会接近于 C 语言的风格. 例如上图中的 “Classes” 就会变成 “Data Structures”.
栗子 - 生成文件的调用关系图
还是一样的套路. 新建 main.c, 输入以下代码:
/** @file
@brief 请不要介意我包含了源文件.
*/
#include "lib.c"
#include "struct.c"
这次, 我在配置文件里还设置了 OPTIMIZE_OUTPUT_FOR_C = YES. 命令行输出, 依次点击链接 Files -> main.c, 可以看到效果如下:
