分类目录

展开|收起

看你喜欢

(1) (1) (42) (1) (1) (1) (16) (2) (1) (1) (4) (1) (2) (7) (4) (1) (1) (1) (1) (3) (1) (5) (1) (1) (1) (1) (1) (2) (1) (4) (4) (3) (1) (1) (2) (1) (37) (2) (1) (5) (3) (1) (4) (1) (1) (11) (3) (1) (9) (3) (1) (23) (2) (1) (2) (1) (1) (1) (1)

最新精华

如何编写makefile【1】- 综述

对于一个大型的软件工程,需要进行必要的模块划分,每个模块的代码又是由很多的源文件组成的,如何把所有模块的代码编译成一个最终的版本文件是一个看似很复杂的问题,但实际我们却能很轻易地做到,一般只需点击编译按钮或执行一个命令行命令即可。我们都知道生成一个程序,需要编译+链接两个过程,但由于我们在学校里大多数人都是用IDE编译一个工程的,只管往里面加文件,点击按钮就可以启动IDE的编译。实际这就造成我们对命令行的编译方式几乎是从不关心。参加工作以后,公司里面的版本编译常常没有IDE了,全靠命令行的编译方式,所以造成大家理解版本编译过程的困难。

实际上理解版本的编译过程对理解代码的整体框架非常有好处,而且对你调试代码也有好处,最有名的就是-g开关选项,经常看一些牛人找到一个什么.mak文件然后去掉或加上一个-g开关,如果你对makefile有点研究,就会发现这不过是一个最简单的gcc编译选项,-g选项是在编译时产生调试信息(一般包括每条指令和代码行号,文件名的对应记录),这样你才可以在调试时用调试器进行设断点,单步调试等。显然这样会增加版本的大小,所以一般在RELEASE版本的编译中会关闭这个开关。而且即使在debug版本调试时,由于代码是按模块编译的,如果你只想调试你想调试的模块,实际你可以把别的模块的-g开关关闭,这样可以减少整个的.o调试版本文件大小。还有一个比较有名的,就是优化选项-O2,工作一段时间后肯定会在某个时候听某个老员工说什么-O2选项没打开,CPU占用率升高。这个-O2(O是字母,优化选项OPTIMIZATION OPTION,不是数值0,呵呵)其实也只是gcc的一个简单的优化选项,其兄弟选项还有-O0、-O1、-O3以及-O等,优化得太多有时候产生的代码其执行结果并不是你想要的,而-O2是一个既能提高代码执行效率又比较安全的选项。那么该在哪里设置这个开关呢?如果你搞懂makefile,你就会发现原来很简单。

我们以前从C语言教材得到的编译和链接是这样的,编译以文件为单位进行的,如.c文件和.s(汇编源文件),最后形成一个.o文件(目标文件),然后多个.o文件再连接成一个可执行文件。一切似乎很简单明了,但这里面隐含了一个重要的细节,而恰恰是这个才是理解编译和链接的关键。就是.o文件是一种可重定位的文件,也就是说可以把.o文件再来一次链接再形成一个.o文件,并可以继续重复这样的操作,这个是靠链接器(linker)的-r选项来实现的,用这个选项链接生成的不是可执行文件(因为这时还有一部分符号没有得到解析,从编译器的术语说就是没有定位),而是生成可重定位输出,也就是说, 生成的输出文件能够再次成为 ld 的输入, 一般称之为不完全(partial) 链接。

GNU的编译工具编译器gcc和ld链接器生成的文件格式如.o以及最终的可执行文件,采用的是一种叫ELF(Executable and linking format)的格式,ELF是x86 Linux系统下的一种常用目标文件(object file)格式,有三种主要类型:
    (1)适于连接的可重定位文件(relocatable file),可与其它目标文件一起创建可执行文件和共享目标文件。经常见到的.o文件(目标文件)就是采用这种格式。
    (2)适于执行的可执行文件(executable file),用于提供程序的进程映像,加载到内存执行。
    (3)共享目标文件(shared object file),连接器可将它与其它可重定位文件和共享目标文件连接成其它的目标文件,动态连接器又可将它与可执行文件和其它共享目标文件结合起来创建一个进程映像。这个描述看起来有些绕人,实际所谓的动态链接库文件如.so文件就是这种格式,对应windows下的PE文件格式就是.dll动态链接库文件,这样你就好理解了。

链接器(linker)的有个-r选项,非常“神奇”,给多模块/模块下多文件的代码结构提供了编译的思路,就是层层分解法:每个模块先编译出自己的各个源文件,然后把这些源文件的.o文件用-r选项链接成模块的.o文件,其它模块同样如此处理。最后,把所有模块的.o文件连接成一个可执行文件(这时候不带-r选项)。

从上面的分析看,编译版本无非就是这样层层的处理。如果手工处理是很枯燥无味而且费时费力的事情,GNU早就考虑到这个问题了,为我们提供了make程序来完成这件事情。make程序执行一种叫makefile的脚本文件,我们可以在makefile文件中书写编译规则(如规定各个模块的代码如何编译链接)。这样,我们就能气定神闲地敲一个make就能完成版本的编译了。但是显然真正的编译过程是受makefile文件控制的。

也许你会想,这个makefile看起来没什么复杂的嘛,编译一个文件和编译一百个文件似乎没有多大区别。这个就小视了makefile的功能了。举个例子,当我们修改了一个头文件,我们要重新编译版本,最笨的办法就是把所有文件都重新编译一遍,版本代码有好几万个文件,都编一遍需要不少时间,真要这样你会崩溃的,因为时间都花在等待编译结果上了。而实际上只有少数的源代码文件引用了这个头文件,也就是只有这些源文件需要重新编译。Makefile就能帮你自动找到需要重新编译哪些文件,而且能为你自动重新链接成新的版本。提前说一下,这个是利用了gcc的-M选项来自动产生依赖文件的机制来实现的。

还要说明的是makefile不仅可以用来编译版本,还可以为你做其它事情,如pc-lint,拷贝文件到某个目录等等。Makefile编译版本只是调用指定的编译器和链接器,以及其它编译过程中所需要的工具。具体运行什么程序来完成你要做的事情,makefile不作任何限制,当然这个程序需要能在shell中运行。简单说,这个makefile有点类似批处理文件。IDE集成编译环境一般会自动为你生成makefile文件,既然编译都靠鼠标点击,为何还要让你来自己编写makefile呢?所以很多人基本就忽略了它的存在,用惯了VC的同学就是这个感觉,但几乎可以肯定的说,你对整个版本的架构理解肯定有所欠缺,虽然代码写得呱呱叫,但也会时常纳闷我负责的模块代码怎么就跑起来了的呢,我压根就没见到main函数。Linux下有个automake,号称可以为你自动生成makefile,但显然也要遵循一定的规则,小编这里建议您先把makefile基本搞懂了,再去研究下automake是咋回事。

表1 makefile学习涉及到的一些术语
术语 英文定义 说明
make 一个用来执行makefile文件的工具软件,它根据makefile文件文件里定义的规则,生成规则里生成的目标,如目标可能是一个最终的版本文件,也可能仅仅是个名称。一般使用GNU的make工具,make实际就是一个程序
makefile 顾名思义,就是make工具要执行这个file。一般大家都知道makefile用来编译版本,但实际上还可以做其它很多史前,比如Linux下的包的安装。Makefile是一个默认的文件名,就是说如果你make时不指定任何文件名,它就在当前目录下找这个文件执行。可以在make时指定另外一个文件,里面写的也是按make语法写的规则,也就是说文件名其实不重要。
GNU GNU’s Not Unix 提供跨平台的工具集,广泛采用的包括GNU Make系列工具(cc,ld,nm,objcopy…)
gcc GNU C compiler GNU的C编译器,实际也可用来编译汇编语言.s文件(间接调用汇编器)
ld GNU linker GNU的链接器
ELF Executable and Linking Format 可执行和链接格式,ELF文件格式有三种类型:可重定位(relocatable),可执行(executable)以及共享object类型,通俗说最后一种就是动态链接库.so文件用的类型

  打分:5.0/5 (共1人投票)
(浏览总计: 174 次)
Add Comment Register



发表回复

  

  

  

您可以使用这些HTML标签

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>