« 解决 RTorrent 部分中文文件名乱码 | 返回首页 | IDE 不是程序员的唯一选择(终) »

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

这里出现了一个 $(MAKE) 没有定义的变量。这个变量是由 Make 自己定义的,它的值即为自己的位置,方便 Make 递归调用自己。在以后的篇章里,我们会继续发现 Make 自身递归调用的威力。


还有问题吗?

自然是有的。

.depend 文件并不能自动随着项目源码的修改而变更。这显然不够自动化。只不过我们在改进这一点时,需要先问一下自己,你的项目真的需要这个吗?很多项目,并不常变更依赖关系。而作为开发人员自己,在不段的修改代码时,心里非常清楚什么时候应该重新 gmake depend 。而对于第一编译项目,且很有可能不再修改源代码重新编译的人来说,生成一次 .depend 文件足够了。

我们可以简单的让 .depend 文件每次重新生成,这或许比仔细检查每个文件的变更情况然后重新生成更为廉价。(为什么?怎么做?这两个问题同学们自己思考:)

简单的在 .depend : 后加一个依赖关系,比如 .depend : $(SRCS) 行不行?在这个例子里没有问题,但是如果更复杂的情况下,比如 .h 文件中又包含了新的 .h ,可能就不能正确的重构 .depend 文件了。

另一方面,.depend 包含了所有 .c 文件和 .h 文件的依赖关系,因为一个文件的变更而重新扫描所有源文件值得吗?

这个问题不好回答。如果项目太大太大,或许有性能问题。不过,为什么你要把这么大的项目所有源文件放在一个子项目中?要知道,使用 Make 构建子项目,比如说一个静态库,是非常廉价的。(这个问题以后再来探讨)

无论如何,没有不能解决的问题。我们自然可以去生成独立的依赖关系描述文件,然后把依赖关系的描述文件和构建过程本身建立依赖关系。这个做起来比较复杂,但是也很有趣。(提示:gcc 的新版本命令行支持 -MMD ,可以把 .o 换为 .d ,这个功能目的何在?)不过,云风提醒大家,切勿钻牛角尖。一切工具只是帮助我们更便捷的达到目标。我们要认清目标,再选择好工具和恰当的用法。


今天就到这里,下一次我们继续讨论另外几个常见问题:如何管理和组织源文件树木更多的项目,怎么组织负责的源代码目录树。以及怎样像 VS 做的那样,为 Release 版和 Debug 版的目标文件分目录存放,并保存源代码目录的清洁。

Comments

@alex
"1,vs+va的效率不是盖的,请使用最新版本的va并仔细的配置试试看,写代码效率比vim本身高很多。"

vs+va是极好的,我开发游戏vs+va用了它至少三年,用它也用到不用鼠标全键盘快捷键,自认为没有什么特性是不清楚的。不过我想说vim配置好了更快,现在转为vim用了四五年,不会再想用vs+va写代码。没有调查,没有发言权。我认为应该完全摸透了vim再来评论它。

"2,vs有一堆插件,例如模拟vim的viemu,可以模拟90%以上的vi操作"

vim的强大在于和它的vim兄弟插件们一起工作,"vsvim,viemu"这些vs插件只是简单模拟基本操作,10%也许有,90%从何谈起。看看插件easymotion的gif截图也许就能理解为什么没有90%的操作https://github.com/Lokaltog/vim-easymotion

"3,并非用了ide就如何蠢笨了,因为甚至有比vs更傻的ide,例如Telelogic的Rphapsody(目前被迫用的东西)。用了gcc和gdb并不能体现水平高在哪里。"

使用ide并不蠢笨,但在一棵树上吊死就是蠢笨。开发有时候是无法离开ide的,xcode,vs,eclipse等等。但对程序员来讲大部分是写代码的时间,那并不需要在ide中进行.有时候程序员也不仅仅只在c++中工作,python, lua, ruby, objectc, java,c#。如果能有更高效并且各平台各语言都通用的方式编辑代码,为什么不试试

完全不用鼠标是不可能的吧??你不google?不查文档?

1,vs+va的效率不是盖的,请使用最新版本的va并仔细的配置试试看,写代码效率比vim本身高很多。
2,vs有一堆插件,例如模拟vim的viemu,可以模拟90%以上的vi操作
3,并非用了ide就如何蠢笨了,因为甚至有比vs更傻的ide,例如Telelogic的Rphapsody(目前被迫用的东西)。用了gcc和gdb并不能体现水平高在哪里。

一直以来,就有鄙视微软的传统,好像用了非微软就如何高明了。其实我一直在非微软环境下开发东西,但是一直喜欢微软的开发环境。

工具会用的话,都是极有效率的。

明白了,虽然还是觉得为了修改了一个文件就导致重新生成依赖有点别扭.但是简单,直接的原则很对,有些问题确实可能是没必要的。

留言本是 MT 自带的,对小于号,大于号的转义没做,懒的弄了。

其实项目初期完全不用去管 .h 的依赖,修改了就 make clean all 就好了。

正如我在第一篇里就写过的,我们应该选用最直接的方法解决问题。其实直接写个 bat 文件,每次去重新编译也未尝不可,对于一两个源程序的小项目,这是最好的方法。

后面的 sed 指令贴不上,算了,想必你也明白我的意思。

浪费了这么多空间,索性多说两句,IDE 的问题不到一定程度是显现不出的,也很难体会。所以云风前辈写这个系列非常有意义,程序员应该能容纳和接受新事物,即便你目前用不到或者无法理解为什么要用。

本质上程序员最终的出路是定制自己的 IDE,从这个方面看,IDE 没错。但没有发展的眼光就错了。

sed 's,\($*\)\.o[ :]*,\1.obj $@ :',g > $@

没贴上
%.d: %.cpp
"makedepend -f- -I Inc $< | sed 's,\($*\)\.o[ :]*,\1.obj $@ :',g > $@"

include $(subst .cpp,.d,$(SRC))

提一点意见,你所提供的处理自动依赖的方法如果在软件最初开发阶段是不是有点繁琐,make 手册里提到了解决这个问题的方式。我提供一个我在 windows 下面的解决方法:

安装 cygwin 和 makedepend 工具包

makefile 中写入规则
%.d: %.cpp
makedepend -f- -I Inc $< | sed 's,\($*\)\.o[ :]*,\1.obj $@ :',g > $@

include $(subst .cpp,.d,$(SRC))

当然还需要 sed ,但应该是 cygwin 的默认安装包了。

为什么在.c/.h中已经描述的依赖,还需要在Makefile中重复呢?而且竟然还是这里的重点。

以前把云风blog收藏了,现在又发现了 猴子灵药

我的一个经验:
前一段时间写一个简单的东西,不想用vs了,就用emacs来写,主要是C和lua。坦白的说,emacs的代码智能即使用cedet、xref也和vs+va不在一个级别上,但是,我发现的一个好处是:可以帮助记单词,多写几遍就记住了标识符的单词,同样也记住了各种变量,为了记忆负担也会尽量使结构简洁,这也算工具简陋的副作用吧。
PS:emacs的其他方便程度自不必说,虽然vs也可以配置到那个程度(甚至快捷键可以配置成emacs模式),但是感觉就交互来说,和emacs原生基于交互的模式来说还是隔了一层。

这里我联想到另一个问题,电脑不是程序员的唯一选择。
比如游戏公司可以去开发game,当然这里的game与olympic game里的所指相同。这样玩家去game的时候,程序员可以在旁边摆个摊炸点油饼卖,这样可以更好地发挥中国程序员的思维方式。

大多数时间选择IDE,纯用vim来写代码是件很辛苦的事,毕竟它不是专门用于编程的编辑器,没有自动补全这些功能让你给变量,函数这些命名的时候都不由自主的选择短名而不是更清晰的长名。每个工具都有各自的专有领域。

不过没有关系,VC 编译 .c 生成 .o 而不是 .obj 就好了。

笔误,gcc吧

vim 的学习成本并不是想象的那么高,现在用vim,脱离IDE,完全用不到鼠标了

云风,如果有空闲时间不如再写本书吧,国内大师稀缺,一些被奉为经典的书几乎全是老外写的。
国内大多数做技术的一方面喜欢着coding,一方面从心理也很抵触coding,我认识好多做技术的,一有机会就都转行了,并不是他们的技术不好,而是在他们看来做技术是没有任何前途的……。这是个急需大师的时代,千呼万唤。
名气越大,责任越大。你不是一个人在战斗,更不是为自己在战斗!!

VIM 很强大,学习成本很高

广义点看问题,vim不也是一个ide吗?还是一个cui层序更容易扩展,更容易和其他工具程序互相通信,因为它的interface很简单。

而gui的东西,各有各的一套,所以互相复用的机会很小。

gui的ide很方便。但很容易让程序员产生惰性,很多只用vs的程序员,连调试符号,编译优化的概念都不知道,可悲啊

感谢云风!

boost的spirit是不是从Haskell的Parsec来的?

喜欢这个系列 学习过了

任何工具都需要学习。编程语言也是工具,何尝不需要学习呢?

许多 C++ 程序员可以日以继夜的研究 C++ 的新特性和古怪用法,却不肯学习 Make 这种简单结构的东西;一边鄙视只会用 VB 拖拖控件的程序员,一边自己离开了 IDE 就不知所措,真是讽刺啊 :)

没错,VB 不只是拖拖控件这么简单,IDE 也有各种高深用法可以钻研,但我们不要在一颗树上吊死。

关于网站提个小意见,是不是提交后如果我在还没出内容的时候再刷新一下就会留两次言呢。还是我的浏览器挂了。。。

make的学习确实不是太容易。还是按需学习一下吧。

嗯?休完国庆,真的有提不起兴致的感觉,不过还是要坚持,接着学习!

Post a comment

非这个主题相关的留言请到:留言本