`
conkeyn
  • 浏览: 1524538 次
  • 性别: Icon_minigender_1
  • 来自: 厦门
社区版块
存档分类
最新评论

makefile 大全

阅读更多

<< >>    

0)
介绍
   
本文将首先介绍为什么要将你的C源代码分离成几个合理的独立档案,什么时候需要分,怎么才能分的好。然后将会告诉你 GNU Make 怎样使你的编译和连接步骤自动化。 对于其它 Make 工具的用户来说,虽然在用其它类似工具时要做适当的调整,本文的内容仍然是非常有用的。如果对你自己的编程工具有怀疑,可以实际的试一试,但请先阅 读用户手册。

1)
多文件项目
1.1 为什么使用它们 ?
    首先,多文件项目的好处在那里呢?
   
它们看起来把事情弄的复杂无比。又要 header 文件,又要 extern 声明,而且如果需要查找一个文件,你要在更多的文件里搜索。 但其实我们有很有力的理由支持我们把一个项目分解成小块。当你改动一行代码,编译 器需要全部重新编译来生成一个新的可执行文件。但如果你的项目是分开在几个小文件里,当你改动其中一个文件的时候,别的源文件的目标文件 (object files) 已经存在,所以没有什么原因去重新编译它们。你所需要做的只是重现编译被改动过的那个文件,然后重新连接所有的目标文件罢了。
   
在大型的项目中,这意味着从很长的(几分钟到几小时)重新编译缩短为十几,二十几秒的简单调整。只要通过基本的规划,将一个项目分解成多个小文件可使你更加容易的找到一段代码。 很简单,你根据代码的作用把你的代码分解到不同的文件里。当你要看一段代码时,你可以准确的知道在那个文件中去寻找它。 从很多目标文件生成一个程序包 (Library) 比从一个单一的大目标文件生成要好的多。 当然实际上这是否真是一个优势则是由你所用的系统来决定的。但是当使用 gcc/ld ( 一个 GNU C 编译/连接器 ) 把一个程序包连接到一个程序时,在连接的过程中,它会 尝试不去连接没有使用到的部分。但它每次只能从程序包中把一个完整的目标文件排除在外。因此如果你参考一个程序包中某一个目标档中任何一个符号的话,那么这个目标 文件整个都会被连接进来。要是一个程序包被非常充分的分解了的话,那么经连接后, 得到的可执行文件会比从一个大目标文件组成的程序包连接得到的文件小得多。 又因为你的程序是很模块化的,文件之间的共享部分被减到最少,那就有很多好处 —— 可以很容易的追踪到臭虫,这些模块经常是可以用在其它的项目里的,同时别人也可以 更容易的理解你的一段代码是干 什么的。当然此外还有许多别的好处 ……

1.2 何时分解你的项目
   
很明显,把任何东西都分解是不合理的。象 世界,你们好 这样的简单程序根本就不能分,因为实在也没什么可分的。把用于测试用的小程序分解也是没什么意思的。但一 般来说,当分解项目有助于布局、发展和易读性的时候,我都会采取它。在大多数的情 况下,这都是适用的。(所谓 世界,你们好 ,既 'hello world' ,只是一个介绍 一种编程语言时惯用的范例程序,它会在屏幕上显示一行 'hello world' 。是最简单 的程序。)如果你需要开发一个相当大的项目,在开始前,应该考虑一下你将如何实现它,并且生成几个文件(用适当的名字)来放你的代码。当然,在你的项目开发的过程中,你可以建立新的文件,但如果你这么做的话,说明你可能改变了当初的想法,你应该想想是否需要对整体结构也进行相应的调整。 对于中型的项目,你当然也可以采用上述技巧,但你也可以就那么开始输入你的代码, 当你的码多到难以管理的时候再把它们分解成不同的档案。但以我的经验来说,开始时在脑子里形成一个大概的方案,并且尽量遵从它,或在开发过程中,随着程序的需要而 修改,会使开发变得更加容易。

1.3 怎样分解项目
   
先说明,这完全是我个人的意见,你可以(也许你真的会?)用别的方式来做。这会触动到有关编码风格的问题,而大家从来就没有停止过在这个问题上的争论。在这里我只是给出我自己喜欢的做法(同时也给出这么做的原因):
    i) 不要用一个 header 文件指向多个源码文件(例外:程序包的 header 文件)。用一个 header 定义一个源码文件的方式会更有效,也更容易查寻。否则改变一个源文件的结构(并且它的 header 文件)就必须重新编译好几个文件。
    ii) 如果可以的话,完全可以用超过一个的 header 文件来指向同一个源码文件。有时将不可公开调用的函数原型,类型定义等等,从它们的C源码文件中分离出来是非常有用的。使用一个 header 文件装公开符号,用另一个装私人符号意味着如果你改变了这个源码文件的内部结构,你可以只是重新编译它而不需要重新编译那些使用它的公开 header 文件的其它的源文件。
    iii) 不要在多个 header 文件中重复定义信息。 如果需要, 在其中一个 header 文件里 #include 另一个,但是不要重复输入相同的 header 信息两次。原因是如果你 以后改 变了这个信息,你只需要把它改变一次,不用搜索并改变另外一 个重复的信 息。
    iv) 在每一个源码文件里, #include 那些声明了源码文件中的符 号的所有 header 文件。这样一来,你在源码文件和 header 文件对某些函数做出的矛盾声明可以比较容易的被编译器发现。

1.4
对于常见错误的注释
    a) 定义符 (Identifier) 在源码文件中的矛盾:
   
在C里,变量和函数的缺省状态是公用的。因此,任何C源码档案都可以引用存在于其它源码档中的通用 (global) 函数和通用变量,既使这个档案没有那个变量或函数的声明或原型。因此你必须保证在不同的两个档案里不能用同一个符号名称,否则会有连接错误或者在编译时会有警告。 一种避免这种错误的方法是在公用的符号前加上跟其所在源文件有关的前缀。比如:
   
所有在 gfx.c 里的函数都加上前缀 “gfx_” 。如果你很小心的分解你的程序,使用有意义的函数名称,并且不是过分使用通用变量,当然这根本就不是问题。 要防止一个符号在它被定义的源文件以外被看到,可在它的定义前加上关键字 “static” 。这对只在一个档案内部使用,其它档案都都不会用到的简单函数是很有用的。
    b) 多次定义的符号:
   
header 档会被逐字的替换到你源文件里 #include 的位置的。 因此,如果 header 档被 #include 到一个以上的源文件里,这个 header 档中所有的定义就会出现在每一个有关的源码文件里。这会使它们里的符号被定义一次以上,从而出现连接错误(见上)。
   
解决方法: 不要在 header 档里定义变量。
   
你只需要在 header 档里声明它们然后在适当的C源码文件(应该 #include 那个 header 档的那个)里定义它们(一次)。对于初学者来说,定义和声明是很容易混淆的。声明的作用是告诉编译器其所声明的符号应该存在, 并且要有所指定的类型。但是,它并不会使编译器分配贮存空间。 而定 义的做用是要求编译器分配贮存空间。当做一个声明而不是做定义的时候,在声明前放一个关键字 “extern” 例如,我们有一个叫 “counter” 的变量,如果想让它成为公用的, 我们在一个源码程序(只在一个里面)的开始定义它: “int counter;” ,再在相关的 header 档里声明 它: “extern int counter;” 函数原型里隐含着 extern 的意思,所以不需顾虑这个问题。
    c) 重复定义,重复声明,矛盾类型:
   
请考虑如果在一个C源码文件中 #include 两个档 a.h b.h a.h #include b.h 档(原因是 b.h 档定义了一些 a.h 需要的类型),会发生什么事呢?这时该C源码文件 #include b.h 两次。因此每一个在 b.h 中的 #define 都发生了两次,每一 个声明发生了两次,等等。理论上,因为它们是完全一样的拷贝, 所以应该不会有什么问题,但在实际应用上,这是不符合C的语法的,可能在编译时出现错误,或至少是警告。 解决的方法是要确定每一个 header 档在任一个源码文件中只被包 含了一次。我们一般是用预处理器来达到这个目的的。当我们进入每一个 header 档时,我们为这个 header #define 一个巨集 指令。只有在这个巨集指令没有被定义的前提下,我们才真正使用 header 档的主体。在实际应用上,我们只要简单的把下面一段码放在 每一个 header 档的开始部分:
    #ifndef FILENAME_H
    #define FILENAME_H
     然后把下面一行码放在最后:
    #endif
     header 档的档名(大写的)代替上面的 FILENAME_H ,用底线 代替档名中的点。有些人喜欢在 #endif 加上注释来提醒他们这个 #endif 指的是什么。例如:
    #endif /* #ifndef FILENAME_H */
    我个人没有这个习惯,因为这其实是很明显的。当然这只是各人的风格不同,无伤大雅。
你只需要在那些有编译错误的 header 档中加入这个技巧,但在所有的 header 档中都加入也没什么损失,到底这是个好习惯。

1.5 重新编译一个多文件项目
   
清楚的区别编译和连接是很重要的。编译器使用源码文件来产生某种形式的目标文件 (object files) 。在这个过程中,外部的符号参考并没有被解释或替换。然后我们使用连接器来连接这些目标文件和一些标准的程序包再加你指定的程序包,最后连接生成一个可执行程序。 在这个阶段,一个目标文件中对别的文件中的符号的参考被解释,并报告不能被解释的参考,一般是以错误信息的形式报告出来。 基本的步骤就应该是,把你的源码文件一个一个的编译成目标文件的格式,最后把所有的目标文件加上需要的程序包连接成一个可执行文件。 具体怎么做是由你的编译器决定的。这里我只给出 gcc GNU C 编译 器)的有关命令,这些有可能对你的非 gcc 编译器也适用。
    gcc 是一个多目标的工具。它在需要的时候呼叫其它的元件(预处理 程序,编译器,组合程序,连接器)。具体的哪些元件被呼叫取决于输入文件的类型和你传递给它的开关。 一般来说,如果你只给它C源码文件,它将预处理,编译,组合所有的文件,然后把所得的目标文件连接成一个可执行文件(一般生成的 文件被命名为 a.out )。你当然可以这么做,但这会破坏很多我们把一个项目分解成多个文件所得到的好处。 如果你给它一个 -c 开关, gcc 只把给它的文件编译成目标文件, 用源码文件的文件名命名但把其后缀由 “.c” “.cc” 变成 “.o” 如果你给它的是一列目标文件, gcc 会把它们连接成可执行文件, 缺省文件名是 a.out 。你可以改变缺省名,用开 -o 后跟你指定的文件名。因此,当你改变了一个源码文件后,你需要重新编译它: 'gcc -c filename.c' 然后重新连接你的项目: 'gcc -o exec_filename *.o' 如果你改变了一个 header 档, 你需要重新编译所有 #include 这个档的源码文件,你可以用 'gcc -c file1.c file2.c file3.c' 然后象上边一样连接。 当然这么做是很繁琐的,幸亏我们有些工具使这个步骤变得简单。

分享到:
评论

相关推荐

    makefile大全

    《深入理解makefile》 在软件开发过程中,makefile是不可或缺的一部分,尤其在Unix/Linux环境下,它扮演着自动化构建和管理项目的关键角色。本文旨在全面解析makefile的原理和使用方法,帮助程序员提升工作效率。 ...

    跟我一起写Makefile.chm

    什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。

    Makefile手册大全.rar

    《Makefile手册大全》是一个关于Makefile的综合资源集合,包含多份文档和教程,旨在帮助用户深入理解和熟练掌握Makefile的编写与使用。在软件开发中,Makefile是自动化构建工具,它定义了项目中各文件之间的依赖关系...

    Makefile 中文教程.pdf

    Makefile 中文教程.pdf Makefile 作为一个自动化编译工具,在软件开发过程中扮演着非常重要的角色。Makefile 的主要作用是根据源代码文件生成目标文件,实现自动化编译和链接的过程。 Makefile 的基本概念 ...

    Makefile进阶 Makefile进阶

    ### Makefile 进阶知识点详解 #### 一、Makefile 文件概述 Makefile 是一种用于自动化构建过程的脚本文件,在 Linux 和 Unix 环境下尤为重要。它可以帮助开发者高效地管理和编译复杂的工程项目,特别是在涉及到数...

    makefile基础知识.pdf

    "makefile基础知识" Makefile 是一种自动化编译工具,常用于大型开发项目的管理和维护。它可以将复杂的开发项目分解成多个易于管理的模块,並高效地处理源文件之间的复杂关系。 Makefile 的主要功能是描述源程序...

    codeblocks工程转makefile

    然而,有些情况下,开发者可能希望脱离IDE,直接使用命令行工具如`make`来编译项目,这时就需要将CodeBlocks的工程文件转换为`makefile`。`makefile`是一个文本文件,它包含了构建项目的规则和指令,使得编译过程...

    跟我一起学Makefile.pdf

    跟我一起学Makefile.pdf Makefile是嵌入式工程师开发必备的经典教程,以下是从文件中提取的相关知识点: 1. 关于程序的编译和链接:Makefile的主要作用是自动化编译和链接过程,使得开发者可以更方便地管理项目。 ...

    makefile使用大全

    ### makefile使用大全知识点解析 #### 一、概述 - **makefile的重要性**:对于Windows程序员来说,由于IDE(如Visual Studio)等工具的存在,makefile的概念可能较为陌生。但在Unix/Linux环境下,makefile是非常...

    Makefile 多目录编译Demo

    在编程世界中,Makefile是一个极其重要的工具,它允许开发者自动化构建项目,特别是当项目包含多个目录和大量源代码文件时。"Makefile 多目录编译Demo"着重讲解了如何在复杂的项目结构中有效利用Makefile进行编译。...

    韦东山通用Makefile文件

    本程序的Makefile分为3类: 1. 顶层目录的Makefile 2. 顶层目录的Makefile.build 3. 各级子目录的Makefile 一、各级子目录的Makefile: 它最简单,形式如下: EXTRA_CFLAGS := CFLAGS_file.o := obj...

    跟我一起写makefile.pdf

    Makefile编译和链接基础知识 Makefile是 Unix 和 Unix-like 操作系统中一种工具,用于自动编译和链接程序。Makefile 文件中包含了一系列规则,指定了编译和链接的过程。下面将详细介绍 Makefile 的基本概念和使用...

    Makefile手册_中文版.pdf

    Makefile是Linux系统下的一种构建工具,它定义了程序编译和链接的所有规则,使得开发人员能够通过一个简单的命令make来自动构建可执行程序。Makefile手册为读者提供了全面的Makefile使用指南,介绍其语法、编写规则...

    Makefile自动构建方法

    ### Makefile自动构建方法 #### 一、概述 在Linux平台下,Makefile是一种非常重要的自动化构建工具,它能够帮助开发者高效地管理项目构建过程。本文档将详细介绍如何使用Makefile进行自动构建,并通过一个简单的...

    Makefile实战.pdf

    Makefile 实战详解 Makefile 是一个非常重要的工具,在软件开发过程中扮演着非常关键的角色。Makefile 的主要作用是告诉 make 程序如何将源代码编译成可执行文件。Makefile 的好坏直接影响着项目的开发效率,一个好...

    跟我一起学Makefile

    《跟我一起学Makefile》是针对Linux环境下的Makefile编写技术进行深入讲解的资源集合,旨在帮助初学者和开发者快速理解和掌握Makefile的编写规则与技巧。Makefile在软件开发中扮演着重要的角色,它是自动化构建工具...

    Makefile写法及自动化工具

    Makefile是Unix/Linux环境下用于自动化构建、编译和链接程序的重要工具。它允许开发者通过定义一系列规则来指示编译过程,从而极大地简化了大型项目中管理多个源文件的复杂性。在【标题】"Makefile写法及自动化工具...

    Linux_Makefile实验.pdf

    Linux Makefile 实验文档深入探讨了Makefile在Linux和Unix系统中的应用,以及如何使用GNU make工具来自动化编译和构建项目。Makefile是一个自动化编译的工具,能够根据文件的时间戳来决定哪些文件需要编译,从而提高...

    Windows 下的 Makefile 编写(一)Makefile的基本规则

    ### Windows 下的 Makefile 编写(一)Makefile 的基本规则 #### 一、Makefile 概述 Makefile 是一种广泛应用于软件构建过程中的自动化脚本文件,主要用于描述项目构建过程中的依赖关系和构建步骤。对于习惯于集成...

Global site tag (gtag.js) - Google Analytics