构建 build
整理为知笔记中和 make makefile automake 有关内容,成此篇。
从 gcc 说起
在介绍 make 之前,首先要保证你会使用 gcc/g++ 编译。了解 gcc 执行的四个阶段,知道预编译阶段、链接阶段需要什么,做什么操作;知道预编译时找不到头文件,链接时缺少库文件怎么处理;知道动态库、静态库的区别,怎么创建并使用它们。以上是必需的!只有掌握了上面的内容,才能保证面对一个简单的项目(可能包含头文件、源文件和链接库)知道怎么组织,最终得到可执行文件。
维基百科的描述:
GNU编译器套装(英语:GNU Compiler Collection,缩写为 GCC),一套编程语言编译器,以GPL及LGPL许可证所发行的自由软件,也是GNU项目的关键部分,也是GNU工具链的主要组成部分之一。GCC(特别是其中的C语言编译器)也常被认为是跨平台编译器的事实标准。
gcc 编译过程
预处理、编译、汇编和链接。学习 gcc,比较重要的是预处理和链接:
参考 gcc编译4个阶段
预处理阶段
预处理器(cpp)根据以字符#开头的命令(directives),修改原始的C程序。
也就是说预处理阶段,会查找并加载头文件生成一个新的C程序。
编译阶段
编译阶段会将代码翻译成汇编语言。
汇编语言是非常有用的,它为不同高级语言不同编译器提供了 通用的语言。如:C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。
汇编阶段
汇编阶段将汇编语言程序转换成为目标文件,也就是二进制机器码。
链接阶段
该阶段将用到的一个或多个目标文件(库文件)链接生成可执行文件。
在预编译阶段包含进来的“stdio.h”中只有“printf”函数的声明,而没有函数的实现,那么,是在哪里实现的“printf”函数呢?答案是:系统把这些函数实现都做到名为libc.so.6的库文件中去了,链接阶段gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf” 了,而这也就是链接的作用。
从上面可以看出,链接时需要目标文件的名称及其所在目录。
常用参数
知道了预处理器(cpp)要读取头文件,那么报错“xxx.h:No such file or directory”就需要确认我们是不是指定了查找目录,或者核对我们指定的路径是否包含该头文件;同样的道理,链接器(ld)需要链接目标文件,和前者稍有不用的地方在于:头文件一般都写在源代码中,我们只需要指定头文件路径;而链接目标文件时,既需要我们指出目标文件所在的目录,同时还需要我们指定使用目标路径下的哪个文件。
以上算是原理。知其所以然之后,我们来看工具具体的使用方法:
- 头文件相关的: -I 后跟绝对路径或相对路径,例如
-I ./include
。预处理器会优先在 @搜索路径 - 链接过程: -L 后跟绝对路径或相对路径,例如
-L ./lib
, -l 后跟库名,例如-lmath
,对应库文件 libmath.a 或 libmath.so - -c:使用源文件生成对应的目标文件,而不进行链接,使用 ,例如
g++ -c main.cpp -o main.o
- -Wall:使 gcc 产生尽可能多的警告信息,并非全部。
- -Werror:把警告当做错误处理,即产生 warning 时就停止编译操作。
特殊的参数:
-nostdinc:使编译器不在系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置。
-Wl,-rpath:在编译过程指定程序在运行时动态库的搜索路径,示例:
1
2# 当指定多个动态库搜索路径时,路径之间用冒号":"分隔
gcc -Wl,-rpath,libPath -L libPath -ltest hello.c将搜索路径信息写入可执行文件(rpath代表runtime path)。这样就不需要设置环境变量。坏处是,如果库文件移动位置,我们需要重新编译test。
…
更多的参数,更详细的使用说明可以查看 官方手册,或者使用 man info –help 等方式获取。
搜索顺序
测试环境:
1 | vimer@debian8light:~/code/test_search$ uname -a |
在 gcc version 4.9.2 (Debian 4.9.2-10) 环境下实际测试,linux 编译时头文件的搜索路径:
- 搜索会从 -I 开始
- 再找 /usr/lib/gcc/x86_64-linux-gnu/4.9/include
- 再找 /usr/local/include/
- 查找 /usr/include/
此测试只是确定以上 4 者相对的先后。测试时未包含更多的潜在的搜索路径,比如 gcc 的环境变量 C_INCLUDE_PATH、CPLUS_INCLUDE_PATH、OBJC_INCLUDE_PATH
两种不类型的头文件 #include<>
和 #include""
搜索规则:
- 使用<>包含的头文件一般会先搜索-I选项后的路径(即用gcc编译时的-I选项,注意是大写),之后就是标准的系统头文件路径。
- 而用””号包含的头文件会首先搜索当前的工作目录,之后的搜索路径才是和<>号包含的头文件所搜索的路径一样的路径。
事实上,知道上述提及的搜索路径即可。不必学究式地死记硬背之间的搜索顺序,能有多少意义呢?。
更直接、简单的方式,确认查找头文件时的路径搜索顺序。参考 使用gcc时头文件路径和动态链接库路径, 通过使用 -v 参数看到:
1 | #include "..." search starts here: |
在此不再具体测试编译过程中链接库文件时的搜索顺序、运行时动态库的搜索顺序,真正需要用之间的搜索顺序时再写代码测试。
动态链接和静态链接
在“链接阶段”一节中出现了库文件“libc.so.6”。@Linxu 中动态库
@引出静态库
@动态链接和静态链接
make 和 Makefile
make 命令用来解释、执行 Makefile文件。针对开发 C++ 程序来说:Makefile 文件是对项目文件(以源文件、链接库为主,也包括头文件)依赖关系的描述,是对 gcc 命令的有效组织。抛开依赖关系,如果项目不复杂,你也可以用 shell 脚本来组织 gcc 命令,前提是根据依赖关系调整好编译命令的排序。虽然能够得到同样的结果,但是执行的效率不如前者,容错的能力不如前者,耗费的精力更是项目越大越难以承受。事实上:
在make诞生之前,编译工作主要依赖于操作系统里面的类似于“make”、“install”功能的shell脚本。
来看看维基百科中的描述:
在软件开发中,make 是一个工具程序(Utility software),经由读取叫做“makefile”的文件,自动化建构软件。
它是一种转化文件形式的工具,转换的目标称为“target”;与此同时,它也检查文件的依赖关系,如果需要的话,它会调用一些外部软件来完成任务。它的依赖关系检查系统非常简单,主要根据依赖文件的修改时间进行判断。大多数情况下,它被用来编译源代码,生成结果代码,然后把结果代码连接起来生成可执行文件或者库文件。
它使用叫做“makefile”的文件来确定一个target文件的依赖关系,然后把生成这个target的相关命令传给shell去执行。
需要注意的是我们在 Linux 上用的一般是 make 的重写/改写版本 GNU Make,除此之外还有 BSD Make 等。
我们来看怎么写 Makefile 文件。
automake
人工写 Makefile 文件已经满足不了我们了。事实是人越来越懒,项目也越来越大,我们希望电脑帮我们做更多的工作。引用 Wikipedia 中的描述:
GNU Automake 是一种编程工具,可以产生供 make 程式使用的 Makefile,用来编译程式。它是自由软件基金会发起的 GNU 计划的其中一项,作为 GNU 构建系统的一部分。automake 所产生的Makefile 符合 GNU 编程标准。
automake 是由 Perl 语言所写的,必须和 GNU autoconf 一并使用。
延伸
以下概念当做扩展知识保留下来,如果有兴趣可以展开学习: