我心目中,这篇文章的目标读者应该是在 Windows 下完全使用 Visual Studio 或 Borland C++ Builder (现在还有人在用么?)等系列 IDE 开发软件的 C/C++ 程序员。
我并不打算从 GNU Make 这种工具的使用写起,因为如果以上提到的这类同学如果都开始看 gmake 的文档(现在翻译工作已经有人做了),应当已经脱离了纯粹 IDE 开发的人群。本文只是一篇非常初步的入门文章,如果你已经使用过类似 gnu make 的工具构建自己的项目,那么完全不必看下去了。
不可否认,IDE 对于软件开发领域,是一项伟大的发明。它极大的降低了软件开发的门槛。但是另一方面,IDE 也限制了程序员们创造软件的手段。这些限制还包括了平台限制,工具选择,甚至新的编译技术,编程语言的选择。所以 IDE 绝对不是程序员的唯一选择,如果你现在作为一个程序员,完全不能离开 IDE 工作。那么,是时候接触一些新东西了。
如果你大约知道一点相关的知识,但是对用 make 工具去构建项目充满了鄙视和厌恶,云风不期望通过这篇文章改变你的想法。因为我不想花太多笔墨来介绍其好处。我个人认为,那些好处,一旦你认真的采用这种开发方式,是显而易见的。
阅读本文,云风假设你至少已经了解了下面这些知识:
会使用 Visual Studio 的某一个版本创建一个由 C/C++ 程序构建起来的工程,并正确编译运行它。
知道 Windows 里有一个叫做控制台(或终端)的程序,通常用 Win-R 然后输入 cmd 启动它。
知道最基本的 dir cd mkdir del copy 等 Windows 命令行指令,并了解 Windows 文件系统的基本结构。
基本了解 "环境变量" 这个概念,知道 PATH 这种常见环境变量的用途。
如果你对上述概念不甚了解,请运用你使用 google 的技能把它们弄清楚。然后,我们可以开始了。
读到这里还没有离开的同学,机器上应该装有一份 Visual Studio 。我的机器上就装了两个版本,但是差不多三年没怎么用它们做项目了。一份是 Visual Studio 6.0 ,早几年订购 MSDN 宇宙版送的。另一份是从微软网站免费下载的 Visual Studio 2005 Express Edition 。
我自己做项目现在是用 gcc ,并且向同学们推荐这款编译器。主要原因当然不是其免费,如上所述,我们现在也可以从微软免费获得编译器了。使用 gcc 最大的好处就是,一旦你打算切换到别的平台开发软件,可以不用更改你的使用习惯。就算你不离开 Windows ,也需要它来开发你的 psp ,nds ,手机,pda 等等。另外 gcc 一直在更新,它给你带来的好处是更强大的编译功能。如果你有一天像我一样放弃了 IDE ,Visual Studio 不断升级的 IDE 界面以及更华丽的工程管理方案对你也会毫无意义。
不过现在,我们暂时不要切换到 gcc 下。那样变化太大,反而难以接受。常年使用 vs 培养出来的习惯是很顽固的。等你学会了 make 这类工具,就会发现,其实切换编译器是再容易不过的事情了,到时候爱用啥用啥。
现在进入 cmd 控制台模式。输入 cl 回车。如果你的机器上安装了 vs 2005 ,你应该可以看到
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.42 for 80x86 Copyright (C) Microsoft Corporation. All rights reserved.
usage: cl [ option... ] filename... [ /link linkoption... ]
如果是 vc 6 那么也大同小异,
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86 Copyright (C) Microsoft Corp 1984-1998. All rights reserved.
usage: cl [ option... ] filename... [ /link linkoption... ]
如果没有出来,可能是环境变量没有配置好。进入 vs 的安装目录(C:\Program Files\Microsoft Visual Studio 8\VC 或 C:\Program Files\Microsoft Visual Studio\VC98\Bin),运行 vcvarsall.bat (vs2005) 或 VCVARS32.BAT (vc6) 即可。实在搞不定就重新安装 vs ,默认安装选项会帮你设置好所有的环境变量。
好了,如果你成功运行了 cl 这个程序,下面的一切都会很顺利。cl 是微软出品的 C/C++ 编译器,就是把 .c 或 .cpp 的源文件编译生成为 PE 文件的工具。常年使用 IDE 的同学也应该注意到它的存在,就是在你使用 vs ide 时,按下 build 的按钮,下面 output 窗口里也会蹦出正在运行 cl 的信息。
现在创建并进入一个工作目录,比如 C:\project\foo ,我们将在这个目录下进行今天的演示。
用你喜欢的文本编辑器在这个目录下编辑一个文件名为 foo.c C 程序。写点 hello world 之内的东西即可。
接下来使用 cl foo.c cl 将为你生成两个文件,一个是 foo.obj 一个是 foo.exe 。我们应该明白这些是什么了。
现在可以试着运行 foo.exe 了,看看是不是你预期的结果?
好吧,对于这种一个源文件就可以搞定的简单程序,我个人认为,无论从任何角度讲,直接使用 cl 都比用 ide 要来的方便。至少我不用构建一个新的工程项目不是?有时候需要写一些实验性的代码,可以把一堆这样的小程序放在一个目录下,只要它们的文件名各不相同就可 以了。完全不必在硬盘上留下大堆复杂的目录结构。
但这离构建一个项目还远远不够。通常一个 C/C++ 项目都是由很多个 .c .cpp 源文件,以及若干 .h 构成的。
比如,我们再添加一个 bar.c ,希望把 foo.c 和 bar.c 编译链接到一起。
无论是 cl 还是 gcc ,都支持直接在一个命令行写上多个源文件,然后依次编译并链接成最终的 PE 文件。所以最简单的做法是直接
cl foo.c bar.c
我们会看到, cl 生成了三个文件, foo.obj bar.obj 以及 foo.exe
cl 将 foo.obj 与 bar.obj 链接成了 foo.exe ,这个文件名默认是以第一个输入源文件为参考的。
如果我们想换个最终目标文件名怎么办?查一下 cl 的帮助,输入 cl /? 看看。
/? 是微软风格的命令行求助选项,几乎所有的微软编译工具都支持。如果是用 gcc 则是 gcc --help 。
我们在帮助信息里可以找到,能够用 /Fe 来指定最终生成的 PE 文件名。
现在可以用 cl /Fefoobar foo.c bar.c 试试看了,主要 /Fe 和 foobar 间不要留空格。
现在则生成的是 foobar.exe 而不是 foo.exe 了。
调试怎么办?
我猜用惯 IDE 的同学们现在最想问的就是这个了。如果我再来推荐诸如写 log 这类 “原始“ 调试方法,怕是要被人嗤之以鼻了。vs 的用户们肯定习惯了单步跟踪、设置断点、监视变量,等等这种 ”高科技“ 的调试手段。其实我也喜欢,方便的工具为何不用呢?
调试器的选择并不多,尤其在 Windows 下。但也不是别无选择的。使用 cl 自然就要使用 vs 自家的调试器啦。正如你使用 gcc 就必然选择 gdb 一样。(btw, gdb 习惯后其实并不难用,何况还有 insight ddd 这样的图形界面的选择,只不过他们的表现不如在非 windows 平台上那么好罢了。)
要使用调试器,必须先生成调试信息。对于 cl ,这个编译开关是 /Zi 不太好记,但是用的多了就熟了,一旦忘记了,请用 cl /? 查询。如果以后换成 gcc 那么就是 -g 了。
试试
cl /Zi foo.c
除了原来生成的 obj 和 exe 文件外,cl 还生成了 pdb 文件,这里面就存放了调试信息。当然,如果你用 gcc 的话,是不会有额外的文件的,调试信息就放在 exe 文件内部。
现在开启熟悉的 ide 来调试这个程序,当然这次,我们只是把 ide 当成调试器在用。如果不想使用 VS 的 IDE ,也可以使用微软免费提供调试器 WinDBG ,不过使用稍微麻烦一点,需要自己设定源码以及调试符号文件的路径,这里就不介绍了。
如果是在使用 vs2005 express 版, 请输入 vcexpress foo.exe ;如果是 vc6 的话,输入 msdev foo.exe 。效果都是一样的,都是打开 vs 的 ide 并加载 foo.exe 。
如果你讨厌命令行操作(嘿,同学。你都打算学习怎样不使用 ide 开发项目了,怎么能讨厌命令行环境呢?), 可以先打开 vs 的 ide ,然后从菜单里选择 open ,打开 foo.exe 也可以。
接下来,按一下 F11 (vs ide 默认的 step into 的热键)。看到了什么?foo.c 被打开了,程序运行指示光标停在了 main 函数的第一行。:)
接下来,云风将传授一门关于调试的独门秘籍。
知道调试器是如何实现断点这个功能的吗?其实它偷偷的在你的程序要设置断点的位置放置了一条调试中断指令。在 x86 32 位系统上,这是一条单字节指令,汇编代码是 int 3 。cpu 运行程序的时候,碰到 int 3 就会把控制权交给调试器,当你在调试器中选择继续运行的时候,调试器再将被替换的程序机器指令换回去,让程序继续运行。
知道了这个细节,我们就可以自己提前设置调试断点了。我称它为硬断点。只要程序运行到,就一定会停下来。如果你想在运行期屏蔽硬断点,需要在源码上做一些工作了。
现在在你刚才的 foo.c 的程序入口处加一行 __asm int 3 ( 如果你在用 gcc 可以加 asm ("int $3"); ) 重新用 cl /Zi foo.c 编译一次。然后在命令行直接运行 foo.exe 。
马上,你将会看到一个熟悉的关于程序崩溃的对话框。没关系,它是由你插入的硬断点 (int 3) 造成的。如果你正确安装了 vs ,vs 应该已经把自己设置成系统默认的调试器了。点对话框上的按钮,便将启动 vs 的 ide ,我们会发现程序正好停在了 int 3 那行汇编的地方。现在你可以尽情的单步跟踪了。
写了这么多,似乎还没进入正题。我们一直在玩一些玩具代码和迷你工程。貌似离取代 ide 去工作还很远。只是我今天写累了,还是且听下回分解吧。今天这一篇,让一些完全没接触过命令行编译程序的同学们加深一下,一组源代码是如何生成最终的执行 文件,这个过程的理解。达到这个目的就足够了。本质上,IDE 也在做这些事情,作为一个 C/C++ 程序员,怎能对一个天天在用的系统怎样在工作一点都不了解呢?
ps. 希望看到这里的同学不要为在命令行下输入了太多的指令而心烦意乱。或者担心那些命令行参数用完就忘。你知道 windows 下有个叫做批处理的好用的工具么?就是那些后缀为 .bat 的文件。可以把经常输入的指令放在里面,简化日常的命令行操作。如果不知道,那么还请 google 之。
虽然 windows 下的批处理比 *nix 的 shell 脚本弱了上万倍,但毕竟还是可以提高我们的生产力的。在没有介绍 make 工具前,同学们可以先用 bat 文件顶一下。比如将你的工程的编译指令一次性写到一个 .bat 文件里,就不需要每次重新编译都敲上长长的一串编译指令了。
事实上,云风最早从 ide 里出来,就是用批处理来管理自己的工程的。在下一回,本系列将隆重推出更加好用的工具来取代它。
话接上回, 话说我们已经大致了解了 C 编译器的工作流程,知道了 IDE 在背后如何在驱动编译器生成代码。对于传统 IDE ,就是集成了编辑器、项目管理、编译器,和调试器等几个大件的一个庞然大物。其中 IDE 企图节省人力的最大的部分就是将源代码组织起来,自动生成其间的关系,调用编译器构建项目。
(此处删去几百字关于 IDE 优劣的讨论。因为我觉得这个话题会陷于无谓的争论,还是直入主题比较好。承接上篇的宗旨,本文只写给有兴趣学习相关知识却不知该如何入门的朋友。说服程序员放弃 IDE 不是本文的初衷。)
对于不太大的项目,比如学校里日常做作业。写一个 make.bat 文件管理你的 C 代码已经足够了。如果你用一些 windows style 的编辑器,比如流行的 editplus 之流,都可以配置相应的所谓 user tool,一键调用 .bat 构建出最终的程序。也可以设置捕获编译器的输出,方便的双击错误信息定位到源代码编译错误的地方。
如果想摆脱鼠标(纯键盘操作对于基于命令行的工作方式来说,非常的有效率。毕竟写程序最终也得靠键盘的。),可以考虑使用 vim 。不要惧怕学习新事物。任何被公认优秀的工具,都有学习的价值。vim 属于有一定学习门槛,但一旦掌握(一般程序员可能需要一周左右的时间熟悉),威力无穷的那种。我的另一个同事强烈推荐 emacs ,我没怎么用过。如果你打算写 10 年以上的程序,花上几天时间学习一个无数程序员公认好用的工具,这项投资我个人认为是非常值得的。
使用 bat 的方式来构建项目有一个问题,那就是当项目慢慢增大时,效率受到影响。因为每次 build 一次,都要重新编译链接所有的源代码。而 C/C++ 从设计之初就考虑到节省编译时间,可以每个源文件独立编译的,然后最后在把分别编译出来的目标文件链接在一起。(关于编译和链接的概念,不明白的同学请自 己 google )下一步,我们可以每次只编译刚刚修改过的源文件,和那些没有修改过的源文件以前编译出来的目标链接在一起就够了。
早期的 C 编译器,但是将编译和链接过程分开由两个程序完成的(现在其实也是)。我们今天看到的类似微软的 cl 这种程序只是给独立的连接器加了个壳而已(gcc 也是这样)。下面我们来看怎样分开编译多个源文件,并链接它们。
如果只想编译一个 .c 文件,只需要在给 cl 加上参数 /c ( gcc 是加 -c )。那么编译上一篇中提到的例子 foo.c ,即用命令行指令:
cl /c foo.c
我们可以看到,当前目录下生成了目标文件 foo.obj
我们可以用同样的方法生成 bar.obj 。即 cl /c bar.c
那么如何把 foo.obj 和 bar.obj 链接起来?还是用 cl 即可。
cl /Fefoobar foo.obj bar.obj
cl 这个壳会正确的识别输入文件的类型,做出对应的链接这个行为。我们也可以直接调用微软的链接器:Link 。
link /out:foobar.exe foo.obj bar.obj
注意,这里输出文件必须写全 .exe 后缀。
罗罗嗦嗦写了一大堆,主要是想加深以前不太明白这些的同学们的印象。如果是玩 IDE 的老鸟,其实在 IDE 的各种设置中,差不多也都看过这些了。关于编译器的命令行参数不记得不要紧。一则可以用 /? 看帮助;二还可以去 VS 的 IDE 中对应的设置菜单里看看,通常一个 IDE 菜单选项的修改,都对应了最终命令行参数的差别。生成 exe 还是生成 dll ;使用控制台模式,还是标准 Win32 程序;预定义些什么宏,打开调试信息或是开启编译优化,无所不在。
记住这些参数的写法并不容易,也没有必要。因为相对于编写代码,敲打这些编译开关只占很少的工作。但要记住,如何调用编译器去构建工程,这个步骤本身其实也是项目构建的一部分。亲手写过,也就多了一分了解。
编程之道在于,让机器做机器的事,人专注于人的思考。如果真的靠手键入每条编译指令,无疑让程序员去做了机器之事。把编译指令写入批处理(脚本)文件,仅仅只是节省了每次编译的重复劳动,并没有从根本上解脱。
在从 C 的源代码构建最终的执行文件这个流程中,哪些是人的创造,哪些是机器应行之事?显而易见,程序员应该做的是:
-
教会机器,如何把一个 .c 文件编译成 .obj 文件。
-
教会机器,如何把若干个 .obj 文件链接成 .exe 文件。
-
告诉机器,你的项目由那些 .c 文件构成,最终你想生成一个叫什么名字的 .exe 文件。
其中前两步,对于大多数类似的项目来说是共同的,所以我们只用也只应该教机器一次。而第三步,提供一个文件列表和一个目标文件名给机器即可。
实现以上目标,显然我们需要额外的工具。IDE 的项目管理及构建模块是一个选择,但不是唯一的选择。IDE 对最为常见的构建需求做的相当不错。也就是说,IDE 完美的教会了机器前两个步骤,我们无须干预。但这件事情意义有多大呢?既然我们现在已经知道整件事情的流程手工怎么进行,完成它就有了无数的选择。教会机 器做这前两件事情,只是一项一次性劳动。我们可以通过教授机器做这件事的过程,学会更多东西。而后举一反三去干更为复杂的工作。下面,云风将介绍一种叫做 MAKE 的工具。它是我们完成这个目的的第一个台阶。
为什么是 Make ?
我想说, make 只是诸多选择中的一个。它绝对不是最好的,但它是最容易理解的。make 以一种极其简单的规则运作,让程序员可以轻易看透它的实质。而简单的工具组合起来往往可以发挥出极大的能量。因为简单,所以 make 也很容易学习。你只需要掌握它很少的一部分功能,它就帮助你完成各种各样的工作。绝不仅仅是编译程序这么简单。你可以用它收发邮件、下载文件、制作安装 包、运行单元测试,等等你能想的到的在你的机器上用命令行能够完成的所有事情。当然这些事情不用 make 也有别的方式去做,只是 make 用起来更方便。Make 会按你告诉它的事情的依赖关系,决定了做事情的前后次序,然后批量完成交给它的任务。如此而已。如果你用过 FreeBSD ,一定会爱上它的 ports 。需要什么软件,进入对应目录,make install 。下载、配置、编译、安装,一气呵成。这就是 make 的威力。
学会了 Make 后,你再接触别的项目构建工具,就不会有太多障碍。Make 做起来比较困难的事,也可以再高一个层次的工具来完成,比如 Automake 。我们要做的是,认识问题是什么,选择合适的工具,用程序员的方式解决它。
Make 有许多分支版本,细节使用起来各有差异。VS 里带了一个叫做 NMAKE 的小工具,是 Make 的一个旁支。我本来想从这个讲起,无奈用的不多,还是换成日常用的比较多的 GNU Make 吧。gmake 的内建指令也更为丰富,稍微熟悉一下,得心应手。更难得的是,在 Windows 下获得 gmake 非常方便,使用 Mingw 版的 gmake 即可,在 google 搜索 “mingw gnu make” 即可。其实不需要安装,它是一个绿色软件,一个独立的一百多K 的 gmake.exe 小程序就可以直接运行。
下面,云风假设你已经正确安装了 Mingw 版的 GNU Make (我的 Windows 系统上安装的版本是 3.81 )。在命令行下,无论在什么目录下都可以直接输入 gmake 运行。(默认安装的 Gnu make 的执行文件名可能不叫这个名字,但你可以自己改个顺手的名字)
让我们开始吧。
还是在一个你可以随意做实验的目录,顶好是你已经创建了 foo.c bar.c 的那个目录下,新建一个叫做 Makefile 的文本文件。编译它,写上:
all : echo Hello World
这是你的第一个 Makefile ,输入请小心。第一行的 all: 应该顶格写,而第二行 echo 之前,必须有一个 Tab ,而不能用空格替代。即:第二行必须是 Tab 打头。
Tab 不是可以忽略的空白字符,并且是 Make 工作的关键。这个设定早就为许多人诟病,没有正确的输入 Tab ,也是许多 Make 初学者常见的错误。骂归骂,只能说这是一个历史原因造成的。好在一旦你习惯它,同样会觉得编写 Makefile 其实是非常顺手的。而且现在很多编辑器都可以让 Tab 明显的显示出来,而不会和空格混淆。
现在,运行 gmake ,你会看到它调用了命令行指令 echo Hello World ,回显了这行字符串。庆祝一下,你成功运行了自己编写的第一个 Makefile 。
Make 的一切设计都是为了简单、快捷。你把要做的事情写在一个文件中,运行 gmake 去跑这些任务。按通常的设计,gmake 应该跟一个任务文件的文件名,指定跑哪个文件。但是为了简洁,gmake 默认去找当前目录下的名为 Makefile 的文件了。至于你想把这些任务放在别的文件中,可以通过 gmake 的参数控制。有兴趣的同学可以按 Gnu 软件的习惯,通过 gmake --help 查询。
光能显示一行 Hello World 显然离题万里。接下来我们看看让 gmake 帮我们编译程序。不要删除前面的 Makefile 文件,再后面追加几行:
foobar.exe : cl /Fefoobar foo.c bar.c
记住第二行开头的 Tab 不要敲漏了。
现在你的 Makefile 文件看起来应该是这样:
all : echo Hello World foobar.exe : cl /Fefoobar foo.c bar.c
我们再运行一下 gmake foobar.exe 看看:gmake 调用了你写好的 cl 指令,编译出了 foobar.exe 这个文件。
OK,大家应该看出点什么。Makefile 比传统的 bat 批处理文件多了点功能。它可以把多个任务放在一个文件里,而不需要我们写多个文件。为了区分任务,我们在命令行指定要做什么。
那些顶行写的文本,如果由一个单词加一个冒号开始。这个单词就被称为一个目标。gmake 目标,就可以做对应的任务了。而 gmake 会去做什么呢?自然由目标定义的下面几行决定。所有以 Tab 开头的文本行,定义了完成这个目标应该执行的命令行指令。gmake 会运行目标定义之下一直到下一个目标定义之间的所有指令。(注:Make 并不会保证这些指令的执行次序,虽然原则上是按你书写次序执行。这可以让 Make 使用多个 CPU 加快运行成为可能)
当我们在命令行输出 gmake all ,它就 echo Hello World ;而输入 gmake foobar.exe 它就调用 cl 去生成 foobar.exe 。如果没有直接输入 gmake 而不跟任何目标参数。它会自动寻找文件里的第一个目标。(而 Makefile 编写的惯例,我们通常把第一个目标起名字为 all ,这仅仅是一个惯例而已)
多试几次看看。
第 2 次运行 gmake foobar.exe 你会发现,gmake 报告:
gmake: `foobar.exe' is up to date.
而拒绝再次编译。
没错,这就是 Make 为数不多的原则之一:一旦目标已经存在,就直接跳过任务。
所有的目标,都被 Make 认为是一个文件,它的规则就是,如果目标文件存在,就认为事情已经做完。除非……
让我们修改一下 Makefile 把 foobar.exe : 这行改成
foobar.exe : foo.c bar.c
然后修改一下 foo.c 存盘,再运行 gmake foobar.exe 试试?又重新编译了对吧。
这就是 Make 的第二条原则:目标 : 后面以空格写上它所依赖的其它目标。如果所依赖目标存在,且比目标本身的时间新,就重新构键一次目标。
由于我们修改了 foo.c 导致了 foo.c 这个文件(对 Make 来说是一个目标,只不过没有构建这个目标的方法而已,只能靠用户自己编辑生成)比 foobar.exe 更新。foobar.exe 依赖 foo.c ,所以触发了 foobar.exe 的构建方法。
同样,我们可以让 all 依赖与 foobar.exe 。这样 gmake all (或省略 all 不写,因为 all 是第一个目标)时,由于不存在 all 这个文件,而触发 foobar.exe 的构建流程。
接下来,我们回头来看看自己编写的这个 Makefile 文件。现在大约是这个样子:
all : foobar.exe echo Hello World foobar.exe : foo.c bar.c cl /Fefoobar foo.c bar.c
第 2 个目标 foobar.exe 的编写非常的累赘。程序员的直觉告诉我们,信息的重复不是一个好味道。那么让我们来修改一下。
foobar.exe : foo.c bar.c cl /Fe$@ $^
这样是不是味道好点了?$@ 和 $^ 都是 Make 的内设变量,也可以看成是一种类似 C 语言中宏的东西。$@ 在指令被运行时,被宏替换为目标,而 $^ 被宏替换为所有的依赖目标,即冒号后面的那一长串东西。同样常用的还是 $< ,可以替换所依赖的第一个目标。
今天已经写的足够多了。没接触过 Make 工具的同学应该能初窥门径了。没错,从现在已经介绍的知识来看,Make 并没有为我们节省太多体力,甚至还多敲了许多字符,今天快结束时,居然还要多记几个诸如 $@ 这样古怪的符号。而做到的事情离我们的目标还很远。
没关系,云风会带着你渐入佳境的,那么,且听下回分解
原文链接地址:http://blog.codingnow.com/2008/09/replacement_of_ide_1.html