因为学习使用Vim,遇到ctags这样一个东东,花了一天时间大致搞清楚了一点,记下来,和大家分享。我写的匆忙,大家也就随便看看好了。希望有用。

一.         ctags 是干什么的

ctags 的功能:扫描指定的源文件,找出其中所包含的语法元素,并将找到的相关内容记录下来。

我用的是 Exuberant Ctags ,在 Windows 上使用,就一个可执行文件,非常绿色,可在 sourceforge 下载。

二.         ctags 可以识别哪些语言,是如何识别的

ctags 识别很多语言,可以用如下命令来查看:

ctags --list-languages

还可以识别自定义语言,具体没研究过。

    ctags 是可以根据文件的扩展名以及文件名的形式来确定该文件中是何种语言,从而使用正确的分析器。可以使用如下命令来查看默认哪些扩展名对应哪些语言:

ctags --list-maps

还可以指定 ctags 用特定语言的分析器来分析某种扩展名的文件或者名字符合特定模式的文件。例如如下命令告知 ctags ,以 inl 为扩展名的文件是 c++ 文件。

ctags --langmap=c++:+.inl –R

    并不十分清楚 ctags 使用何种技术来解析内容,估计包括正则表达式、词法分析、语法分析等等。但 ctags 不是编译器也不是预处理器,它的解析能力是有限的。例如它虽然可以识别宏定义,但对于使用了宏的语句的识别还是有缺陷的,在一些稍微正规点的代码(例如 ACE 的库或 VC 的头文件等)中的某些常规的宏使用方式会导致 ctags 无法识别,或者识别错误,从而使得 ctags 没有记录 user 想记录的内容,或者记录下的信息不准确。另一方面 ctags 也有聪明的一面,例如在 cpp 文件中扫描到 static 的全局变量时, ctags 会记录这个变量,而且还会标明说这个变量是局限于本文件的,同样的定义,如果放在 h 文件中, ctags 则不会标明说这个变量是局限于本文件的,因为 ctags 认为 h 文件是头文件的一种,会被其他文件 include ,所以在其他文件中可能会用到该 h 文件里定义的这个全局变量。

三.         ctags 可以识别和记录哪些语法元素

可以用如下命令查看 ctags 可以识别的语法元素:

ctags --list-kinds

或者单独查看可以识别的 c++ 的语法元素

ctags --list-kinds=c++

    ctags 识别很多元素,但未必全都记录,例如“函数声明”这一语法元素默认是不记录的,可以控制 ctags 记录的语法元素的种类。如下命令要求 ctags 记录 c++ 文件中的函数声明和各种外部和前向声明:

ctags -R --c++-kinds=+px

四.         ctags 是怎么记录的

不管一次扫描多少文件,一条 ctags 命令把记录的内容都记到一个文件里去,默认是当前目录的 tags 文件,当然这是可以更改的。

每个语法元素对应文件里的一行,学名叫 tag entry

1)              开头是 tag 的名字,其实也就是语法元素的名字,例如记录的是函数的话则 tag 名就是函数名,记录的是类的话, tag 名就是类名。

2)              接下来是一个 tab

3)              接下来是语法元素所在的文件名。

4)              又是一个 tab

5)              一条“命令”。这个要解释一下意义: ctags 所记录的内容的一个功能就是要帮助像 vi 这样的编辑器快速定位到语法元素所在的文件中去。前面已经记录了语法元素所在的文件,这条命令的功能就是一旦在 vi 中打开语法元素所在的文件,并且执行了该“命令”后, vi 的光标就能定位到语法元素在文件中的具体位置。所以该“命令”的内容一般分两种,一种是一个正则表达式的搜索命令,一种是第几行的指向命令。默认让 ctags 在记录时自行选择命令的种类,选择的依据不详,可以通过命令行参数来强制 ctags 使用某种命令,这里就不多谈了。

6)              对于本 tag entry (简称 tag )所对应的语法元素的描述,例如语法元素的类型等。具体内容和语法元素的种类密切相关。显示哪些描述,显示的格式等都是可以在命令行指定的。例如如下命令要求描述信息中要包含: a 表示如果语法元素的类的成员的话,要标明其 access (即是 public 的还是 private 的); i 表示如果有继承,标明父类; K 表示显示语法元素的类型的全称; S 表示如果是函数,标明函数的 signature z 表示在显示语法元素的类型是使用 kind:type 的格式。

ctags -R --fields=+aiKSz

    ctags 除了记录上述的各种内容之外,还可以在 tags 文件中记录本次扫描的各个文件,一个文件名对应一个 tag entry 。默认是不记录的,要强制记录要是使用如下命令:

ctags –R --extra=+f

    还可以强制要求 ctags 做这样一件事情——如果某个语法元素是类的一个成员,当然 ctags 默认会给其记录一个 tag entry (说白了就是在 tags 文件里写一行),可以要求 ctags 对同一个语法元素再记一行。举一个例子来说明:假设语法元素是一个成员函数, ctags 默认记录的 tag entry 中的 tag 的名字就是该函数的名字(不包括类名作为前缀),而我们强制要求 ctags 多记的那个 tag entry tag 的名字是包含了类明作为前缀的函数的全路径名。这样做有什么好处见下文分析。强制 ctags 给类的成员函数多记一行的命令为:

ctags -R --extra=+q

五.         vi 大概是怎样使用 ctags 生成的 tags 文件的

估计 vi 是这样使用 tags 文件的:我们使用 vi 来定位某个 tag 时, vi 根据我们输入的 tag 的名字在 tags 文件中一行行查找,判断每一行 tag entry tag 名字(即每行的开头)是否和用户给出的相同,如果相同就认为找到一条记录,最后 vi 显示所有找到的记录,或者根据这些记录直接跳转到对应文件的特定位置。

考虑到 ctags 记录的内容和方式,出现同名的 tag entry 是很常见的现象,例如函数声明和函数定义的 tag 名字是一样的,重载函数的 tag 名字是一样的等等。 vi 只是使用 tag 名字来搜索,还没智能到可以根据函数的 signature 来选择相应的 tag entry vi 只能简单的显示 tag entry 的内容给 user ,让 user 自行选择。

ctags 在记录成员函数时默认是把函数的名字(仅仅是函数的名字,不带任何类名和 namespace 作为前缀)作为 tag 的名字的,这样就导致很多不同类但同名的函数所对应的 tag entry 的名字都是一样的,这样 user vi 中使用函数名来定位时就会出现暴多选择,挑选起来十分麻烦。 user 可能会想在 vi 中用函数的全路径名来进行定位,但这样做会失败,因为 tags 文件中没有对应名字的 tag entry 。要满足用户的这种心思,就要求 ctags 在记录时针对类的成员多记录一条 tag entry ,该 tag entry 和已有的 tag entry 的内容都相同,除了 tag 的名字不同,该 tag entry 的名字是类的成员的全路径名(包括了命名空间和类名)。这就解释了 ctags --extra=+q 这样一条命令行选项(见四)。

六.         我的一条 ctags 命令

ctags -R --languages=c++ --langmap=c++:+.inl -h +.inl --c++-kinds=+px --fields=+aiKSz --extra=+q --exclude=lex.yy.cc --exclude=copy_lex.yy.cc

命令太长了,折成两行了,可以考虑把命令的各个参数写到文件里去了(具体做法就不谈了)。

1.

-R

表示扫描当前目录及所有子目录(递归向下)中的源文件。并不是所有文件 ctags 都会扫描,如果用户没有特别指明,则 ctags 根据文件的扩展名来决定是否要扫描该文件——如果 ctags 可以根据文件的扩展名可以判断出该文件所使用的语言,则 ctags 会扫描该文件。

2.

--languages=c++

只扫描文件内容判定为 c++ 的文件——即 ctags 观察文件扩展名,如果扩展名对应 c++ ,则扫描该文件。反之如果某个文件叫 aaa.py python 文件),则该文件不会被扫描。

3.

--langmap=c++:+.inl

告知 ctags ,以 inl 为扩展名的文件是 c++ 语言写的,在加之上述 2 中的选项,即要求 ctags c++ 语法扫描以 inl 为扩展名的文件。

4.

-h +.inl

告知 ctags ,把以 inl 为扩展名的文件看作是头文件的一种( inl 文件中放的是 inline 函数的定义,本来就是为了被 include 的)。这样 ctags 在扫描 inl 文件时,就算里面有 static 的全局变量, ctags 在记录时也不会标明说该变量是局限于本文件的(见第一节描述)。

5.

--c++-kinds=+px

记录类型为函数声明和前向声明的语法元素(见第三节)。

6.

--fields=+aiKSz

控制记录的内容(见第四节)。

7.

--extra=+q

ctags 额外记录一些东西(见第四、五节)。

8.

--exclude=lex.yy.cc --exclude=copy_lex.yy.cc

告知 ctags 不要扫描名字是这样的文件。还可以控制 ctags 不要扫描指定目录,这里就不细说了。

七.         本文内容来源

    Exuberant Ctags 附带的帮助文档( ctags.html )。

posted on 2010-03-31 21:43  Kevin Bing  阅读(3730)  评论(0编辑  收藏  举报