Cflow 使用详解 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
朝歌  译 
2014-11-8 
 
目录 
1、cflow 简介 ..................................................................................................................... 3 
2、使用 cflow 分析程序的简要方法 .................................................................................. 3 
3、两种类型的流图............................................................................................................. 5 
4、各种输出格式 ................................................................................................................ 7 
5、处理递归调用 ................................................................................................................ 8 
6、控制符号类型 .............................................................................................................. 12 
6.1、语法类 ................................................................................................................... 15 
6.2、符号别名 ............................................................................................................... 16 
6.3、GCC 初始化 .......................................................................................................... 16 
7、运行预处理器 .............................................................................................................. 17 
8、使用 ASCII 码来生成流图 ........................................................................................... 18 
9、交叉引用输出 .............................................................................................................. 20 
10、配置文件和变量 ........................................................................................................ 21 
11、在 Makefiles 中使用 cflow ...................................................................................... 22 
12、cflow 选项的完整列表 .............................................................................................. 23 
13、GNU Emacs 使用 cflow .......................................................................................... 26 
14、附录 wc 命令的源文件 .............................................................................................. 28 
 
 
1、cflow 简介 
cflow 工具用于分析 C 语言实现的源文件集并输出各个函数之间的依赖关系图。 
cflow 可以生成两种类型的图:正向图和逆向图。正向图从 main()函数开始,递
归显示 main()函数调用的所有函数。相反,逆向图是一个子图的集合,使用递
归命令时,为每个函数列出它的调用者。 
除了这两种输出模式,cflow 也可以为输入文件中的所有遇到的符号生成一个交
叉引用列表。 
该程序还提供了对将出现在输出中出现的符号的详细控制,允许忽略用户不感兴
趣的符号。详细的输出格式也是可配置的。 
2、使用 cflow 分析程序的简要方法 
我们先从一个例子开始熟悉 GNU cflow 的用法。假设你已经有了一个 whoami
命令的简单实现,并且你想获得它的函数依赖关系图。下面的是程序: 
/* whoami.c - a simple implementation of whoami utility */ 
          #include 
 
          #include  
          #include  
          #include  
           
          int 
          who_am_i (void) 
          { 
              struct passwd *pw; 
              char *user = NULL; 
           
              pw = getpwuid (geteuid ()); 
              if (pw) 
                  user = pw->pw_name; 
              else if ((user = getenv ("USER")) == NULL) 
                  { 
                      fprintf (stderr, "I don't know!\n"); 
                      return 1; 
                  } 
              printf ("%s\n", user); 
              return 0; 
          } 
           
          int 
          main (int argc, char **argv) 
          { 
              if (argc > 1) 
                  { 
                      fprintf (stderr, "usage: whoami\n"); 
                      return 1; 
                  } 
              return who_am_i (); 
          } 
运行 cflow 产生下面的输出: 
$ cflow whoami.c 
          main() : 
                  fprintf() 
                  who_am_i() : 
                          getpwuid() 
                          geteuid() 
                          getenv() 
                          fprintf() 
                          printf() 
这是一个正向调用图显示了输入文件中的调用者-被调用者依赖关系。每一行以
一个函数名开始,紧跟后面有一对括号来指明这是一个函数。如果这个函数在某
一个输入文件中定义,这一行会使用一对尖括号,其中显示函数的原型和它被定
义的位置。这一行将会以:结尾来表示这个函数调用了其它函数。比如,这一行 
main() : 
显示 main 函数在源文件 whoami.c 中的第 25 行定义,原型是 int  main  (int 
argc,char **argv),最后的冒号表示 main 函数调用了其他函数。 
这一行后面的是被 main 函数调用的函数。每一行都会根据嵌套关系有相应的缩
进。 
通常 cflow 会显示完整的函数原型。然而有时你希望忽略原型的一部分。一些选
项可以用来完成这个功能。使用--omit-symbol-names 选项来打印没有函数名的原
型。使用--omit-arguments 选项来忽略参数列表。这些选项可以被用做很多用途,
其中一个就是使结果图更加的紧凑。为了显示他们的作用,下面是使用上述两种
--omit-选项的结果:main() : 
默认情况下,cflow 从 main()函数开始输出正向图。当分析一个完整的 C 程序集
的时候这是非常的便利的。但是有时候用户可能只想看到从特定函数出发的部分
图。使用—main(-m)选项,Cflow 允许选择这样的功能调用。因此,运行$cflow 
--main who_am_i whoami.c,将会得到下面的结果: 
who_am_i() : 
                  getpwuid() 
                  geteuid() 
                  getenv() 
                  fprintf() 
                  printf() 
3、两种类型的流图 
在前面我们讨论了正向图,用于展示调用者-被调用者的依赖关系。另一种 cflow
输出是逆向图,列举被调用者-调用者的依赖关系。,为了生成逆向图,运行 cflow
的时候要使用--reverse(-r)选项。比如使用下面的例子: 
$ cflow --reverse whoami.c 
          fprintf(): 
                  who_am_i() : 
                          main()  
                  main()  
          getenv(): 
                  who_am_i() : 
                          main()  
          geteuid(): 
                  who_am_i() : 
                          main()  
          getpwuid(): 
                  who_am_i() : 
                          main()  
          main()  
          printf(): 
                  who_am_i() : 
                          main()  
          who_am_i() : 
                  main()  
这个输出包含了几个子图,每一个字图描述了一个特定的函数的调用者。因此,
第一个子图说明函数 fprintf 被两个函数调用:who_am_i 和 main。同时他也被 main
函数直接调用。 
第一个值得注意的地方是在输出中 who_am_i 重复出现了多次。这是一个详细的
输出,为了让输出显得更加简洁,可以使用--brief(-b)选项。比如: 
$ cflow --brief --reverse whoami.c 
          fprintf(): 
                  who_am_i() : 
                          main()  
                  main()  [see 3] 
          getenv(): 
                  who_am_i() : [see 2] 
          geteuid(): 
                  who_am_i() : [see 2] 
          getpwuid(): 
                  who_am_i() : [see 2] 
          main()  [see 3] 
          printf(): 
                  who_am_i() : [see 2] 
          who_am_i() : [see 2] 
在简要输出中,一旦某个给定的函数被写入,随后的关于该函数的调用实例中将
会只包含它的定义和第一次输出行的引用。 
如果输出图比较大,查找需要的行数就会比较麻烦(除非你使用 Emacs 的
cflow-mode)。这种情况下可以使用特殊的选项--number (-n),这样就可以在输出
时显示每一行的顺序标号。使用这个选项,上面的输出将变成这样: 
$ cflow --number --brief --reverse whoami.c 
                  1 fprintf(): 
                  2          who_am_i() : 
                  3                  main()  
                  4          main()  [see 3] 
                  5 getenv(): 
                  6          who_am_i() : [see 2] 
                  7 geteuid(): 
                  8          who_am_i() : [see 2] 
                  9 getpwuid(): 
                10          who_am_i() : [see 2] 
                11 main()  [see 3] 
                12 printf(): 
                13          who_am_i() : [see 2] 
                14 who_am_i() : [see 2] 
当然,--brief 和--number 选项对正向图和逆向图都起作用。 
4、各种输出格式 
前面所描述的输出格式被称为 GNU 类型。除此之外,cflow 也可以使用 POSIX 产
生格式化的输出。这种格式,输出的每一行都以一个参考数字开始,比如,最开
始是输出行的顺序号,后面跟随每一个嵌套层的固定长度的缩进。然后如果有的
话依次是函数的名字、冒号、函数的原型。紧跟在函数原型后面的是定义的位置
(包括文件名和行号)。函数顶一个位置都被尖括号围住。如果函数的定义没有
找到,该行将会以一个空的尖括号结尾。 
使用格式化输出要么在命令行中通过--format=posix (-f posix)选项指定,要么设置
环境变量 POSIXLY_CORRECT 。 
使用 POSIX 格式处理我们的样例文件,如下: 
$ cflow --format=posix whoami.c 
                  1 main: int (int argc,char **argv),  
                  2          fprintf: <> 
                  3          who_am_i: int (void),  
                  4                  getpwuid: <> 
                  5                  geteuid: <> 
                  6                  getenv: <> 
                  7                  fprintf: <> 
                  8                  printf: <> 
是否在输出中要包含函数的参数列表现在并不清楚。默认情况下 cflow 将会全部
打印它们。然而一些程序使用 cflow 分析时希望省略参数列表,这可以使用
--omit-arguments 选项实现。 
cflow 未来的版本中将会提供更多的输出格式,包括 XML 和 HTML 输出。目前你
可以使用 VCG 工具来创建典型的图。根据 xvcg 来转变输出格式可以使用
cflow2vcg 程序在 GPL 下都是可用的。 
Cflow2vcg 期望使用 POSIX 格式图,每层嵌套的缩进为一个水平制表符,第 0 层
使用额外的 tab 字符,在函数声明中没有参数列表。这样用可兼容 cflow2vcg 产
生的输出格式,调用 cflow 如下: 
cflow --format=posix --omit-arguments \ 
                      --level-indent='0=\t' --level-indent='1=\t' \ 
                      --level-indent=start='\t' 
你可以使用下面的脚本来虚拟调用这三个工具: 
#! /bin/sh 
           
          cflow --format=posix --omit-arguments \ 
                      --level-indent='0=\t' --level-indent='1=\t' \ 
                      --level-indent=start='\t' $* | 
              cflow2vcg | xvcg - 
5、处理递归调用 
有时候程序中包含调用自己的函数。GNU 输出格式为这种函数提供了特殊的表
示。在结束字符-冒号之前,会有一个标号‘(R)’作为递归函数的标志。随后递
归调用处会在行结尾使用‘(recursive: see refline)’标识出。这里的 refline 表示递