Reload Original PagePrint PageEmail Page

云风的 BLOG: IDE 不是程序员的唯一选择(五)

国庆休完了,干了许多事情,几乎没闲着。接下来写这个系列,有点提不起兴致的感觉。

如果一直讲 GNU Make 的话,就有点离题了。本来我是想讲讲,离开 IDE ,程序员该如何处理问题的。Make 只是一个起点。写着写着,就已经写的足够的多,可又似乎什么都没讲出来。有心的同学应该已经找到 GNU Make 的中文手册自己去研究了,我想大家若结合自己做过的项目,会发现其中奥妙无穷。而比较乐意享受快餐文化的另一批同学,可能还在等我的下文。该怎么说?还是先引用 VIM 主页上介绍 vim 的一句话,

Vim isn't an editor designed to hold its users' hands. It is a tool, the use of which must be learned.

是的,Make 也是,更多的编程开发工具都是。既然是工具,就必须付出学习成本。如果你觉得使用某种工具不需要支付学习成本,那么你一定失去了一些东西。只不过,你未必意识的到而已。

为了利用计算机帮助我们达到一个目的,原本有许多方法。有普通人的方式,有程序员的方式。程序员的方式是教会计算机代我们去做;普通的人方式是利用计算机协助我们去做。在 IDE 里,有时我们也想办法用独特的方式去教计算机做一些事情。IMHO, C++ 以及其千奇百怪的特性挖掘,建立出丰富的模板库很大程度就是来源于此。比如 boost 里有个叫 Boost::Spirit 的库,可以让 C++ 程序员书写近似的 BNF 的式子,生成一个解析器去分析它。但是为什么不考虑直接用 yacc 呢?它天然的就是为了处理 bnf 而存在的。因为 IDE 并不是一个很好的粘合剂,用来粘合这些工具。所以,我们把粘合层下移到了 C++ 编译器的层面。不幸的是,一旦我们给一个单一工具附加了太多使命后,无论是 IDE 还是 C++ 编译器,它们都会迅速的臃肿,直到不能承担;或者,我们干脆改变问题,让问题去适应解决手段,而不是接受手段去适应问题。

Make 是一个好的起点,它展示了一个功能单一简洁的工具,能且只能完成一个工具粘合剂的使命。并且完全以计算机的方式去工作。工具和工具之间以命令行,返回码,标准输入输出管道的方式藕荷。和人交互的界面是简单可读的文本。层次分明,顾而可堪大任。

多说无益,既然已经没有多少兴致向下写。就把剩下的时间集中在几个前面提到过的问题的解决上。剩下的一切就靠同学们自己研究摸索了。离开 IDE ,你应该得到了一片更广阔的天空,一切适合计算机协作的工具(有命令行接口)都能拿来使用。你不用再局限于别人是否给你提供了源代码,是否有良好的 DLL 插件接口,而只用关心前人做的工具是否适合你现在的需要。你用自己的头脑去发现捷径,而不是 google 一个 step by step 的教程。

问题一,如何让编译流程自动得到 .h 头文件的依赖关系?

C 语言的源文件依赖头文件是一个语言层面的东西,跟 Make 工具无关。有些构建工具,比如我曾经使用过的 boost jam 可以编写代码扫描器来完成这些,但 GNU Make 是没有的。没有并不是坏事,一个优秀的工具的原则应该是:努力做好一件事,并只做这一件。其实,分析源代码找到依赖之头文件,并不是一件容易的事情,至少你需要实现 C 语言的宏的解析工作。这应该是 C/C++ 编译器的一部分。

可惜我在 VC 中没找到编译选项可以只完成头文件依赖关系的处理流程。所以,如果你想在 Windows 下使用机器自动生成依赖关系,还得装一个 gcc 。Mingw 的 gcc 是一个不错的选择。你完全可以用 gcc 去分析头文件的依赖关系,再用 vc 来编译代码,虽然有点奇怪,但有何不可呢?一切工具皆可为我所用。

gcc 在命令行加上参数 -MM ,即可生成 GNU Make 格式的依赖关系文本(包括直接和间接的关系)。这个文本输出在标准输出上。但是,其输入格式里,目标文件的后缀是 .o 而不是 VC 默认的 .obj 。不过没有关系,VC 编译 .c 生成 .o 而不是 .obj 就好了。

下面来看我们前面几篇提到的那个小项目,foo.c bar.c foo.h 最后编译生成 foobar.exe 。我写一个完整的改进版在下面:

SRCS=foo.c bar.c
OBJS=$(SRCS:.c=.o)
MAKEDEPEND=gcc -MM
CFLAGS=/Zi 

all : foobar.exe

.depend:
    $(MAKEDEPEND) $(SRCS) > $@

depend:
    -del .depend
    $(MAKE) .depend

clean :
    -del foobar.exe $(OBJS)

foobar.exe : $(OBJS)

%.exe :
    link /out:$@ $^

$(OBJS) : %.o : %.c
    cl /c /Fo$@ $(CFLAGS) $<

include .depend

这里出现了许多前面没有提到过的陌生用法。下面一一讲解:

SRCS=foo.c bar.c
OBJS=$(SRCS:.c=.o)

仅凭猜测,我们就能知道,OBJS 会被定义成 foo.o bar.o 。GNU Make 有着强大的字符串处理功能,非常适合做这类事情。像这种后缀匹配替换,用这个简单的语法就可以表达了。如果在 SRCS 里定义了多种不同后缀的字符串,比如有 .c .cpp .asm 等等,我们可以先筛选出 .c 的部分,即 CSRCS = $(filter %.c,$(SRCS)) 。这里不再详述。

需要稍加说明的是这里的一个赋值:OBJS=$(SRCS:.c=.o) 。OBJS 的值依赖了另一个变量 SRCS 。那么,OBJS 的求值过程是什么是否发生的呢?这个对于敏感的程序员应该是头脑中立刻显现的问题。

答案是,直到 OBJS 被真正用到,取值的时候,$(SRCS:.c=.o) 的求值过程才被触发。也就是说,你可以在下面修改 SRCS 的值(通过 = 或 += 等)而 OBJS 的值总是正确的。

btw, 如果你希望求值过程立刻进行,可以用 := 而不是 = 。具体请翻阅 GNU Make 的文档。

在 Makefile 的最后,include .depend 包含了一个暂时还不存在的 .depend 文件。当 Make 工作的时候,发现一个文件不存在,都会试图构建它,include 指令中出现的文件也不例外。所以,第一次运行,会触发 .depend 文件的构建。

.depend:
    $(MAKEDEPEND) $(SRCS) > $@

通过调用 gcc -MM ,gcc 会输出类似 foo.o : foo.c foo.h 这样的文本行,借助命令行管道操作,我们把输出定向到了 $@ ,即文件 .depend 中。

而第 2 次运行 Make ,由于 .depend 文件已经存在,依赖关系则不再构建。

如果我们修改了 .c 文件中引用的 .h 文件怎么办?简单的方法就是删除 .depend ,重新 Make 。当然,这里我们提供了一个叫做 depend 的伪目标来帮我们做这件事情。

depend:
    -del .depend
    $(MAKE) .depend

::...

免责声明:
当前网页内容, 由 大妈 ZoomQuiet 使用工具: ScrapBook :: Firefox Extension 人工从互联网中收集并分享;
内容版权归原作者所有;
本人对内容的有效性/合法性不承担任何强制性责任.
若有不妥, 欢迎评注提醒:

或是邮件反馈可也:
askdama[AT]googlegroups.com


点击注册~> 获得 100$ 体验券: DigitalOcean Referral Badge

订阅 substack 体验古早写作:


关注公众号, 持续获得相关各种嗯哼:
zoomquiet


自怼圈/年度番新

DU22.4
关于 ~ DebugUself with DAMA ;-)
粤ICP备18025058号-1
公安备案号: 44049002000656 ...::