构建 build

整理为知笔记中和 make makefile automake 有关内容,成此篇。

gcc 说起

在介绍 make 之前,首先要保证你会使用 gcc/g++ 编译。了解 gcc 执行的四个阶段,知道预编译阶段、链接阶段需要什么,做什么操作;知道预编译时找不到头文件,链接时缺少库文件怎么处理;知道动态库、静态库的区别,怎么创建并使用它们。以上是必需的!只有掌握了上面的内容,才能保证面对一个简单的项目(可能包含头文件、源文件和链接库)知道怎么组织,最终得到可执行文件。

维基百科的描述:

GNU编译器套装(英语:GNU Compiler Collection,缩写为 GCC),一套编程语言编译器,以GPL及LGPL许可证所发行的自由软件,也是GNU项目的关键部分,也是GNU工具链的主要组成部分之一。GCC(特别是其中的C语言编译器)也常被认为是跨平台编译器的事实标准。

gcc 编译过程

预处理、编译、汇编和链接。学习 gcc,比较重要的是预处理和链接:

参考 gcc编译4个阶段

  1. 预处理阶段

    预处理器(cpp)根据以字符#开头的命令(directives),修改原始的C程序。

    也就是说预处理阶段,会查找并加载头文件生成一个新的C程序。

  2. 编译阶段

    编译阶段会将代码翻译成汇编语言。

    汇编语言是非常有用的,它为不同高级语言不同编译器提供了 通用的语言。如:C编译器和Fortran编译器产生的输出文件用的都是一样的汇编语言。

  3. 汇编阶段

    汇编阶段将汇编语言程序转换成为目标文件,也就是二进制机器码。

  4. 链接阶段

    该阶段将用到的一个或多个目标文件(库文件)链接生成可执行文件。

    在预编译阶段包含进来的“stdio.h”中只有“printf”函数的声明,而没有函数的实现,那么,是在哪里实现的“printf”函数呢?答案是:系统把这些函数实现都做到名为libc.so.6的库文件中去了,链接阶段gcc会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf” 了,而这也就是链接的作用。

    从上面可以看出,链接时需要目标文件的名称及其所在目录。

常用参数

知道了预处理器(cpp)要读取头文件,那么报错“xxx.h:No such file or directory”就需要确认我们是不是指定了查找目录,或者核对我们指定的路径是否包含该头文件;同样的道理,链接器(ld)需要链接目标文件,和前者稍有不用的地方在于:头文件一般都写在源代码中,我们只需要指定头文件路径;而链接目标文件时,既需要我们指出目标文件所在的目录,同时还需要我们指定使用目标路径下的哪个文件。

以上算是原理。知其所以然之后,我们来看工具具体的使用方法:

  1. 头文件相关的: -I 后跟绝对路径或相对路径,例如 -I ./include。预处理器会优先在 @搜索路径
  2. 链接过程: -L 后跟绝对路径或相对路径,例如 -L ./lib, -l 后跟库名,例如 -lmath,对应库文件 libmath.a 或 libmath.so
  3. -c:使用源文件生成对应的目标文件,而不进行链接,使用 ,例如 g++ -c main.cpp -o main.o
  4. -Wall:使 gcc 产生尽可能多的警告信息,并非全部。
  5. -Werror:把警告当做错误处理,即产生 warning 时就停止编译操作。

特殊的参数:

  1. -nostdinc:使编译器不在系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置。

  2. -Wl,-rpath:在编译过程指定程序在运行时动态库的搜索路径,示例:

    1
    2
    # 当指定多个动态库搜索路径时,路径之间用冒号":"分隔
    gcc -Wl,-rpath,libPath -L libPath -ltest hello.c

    将搜索路径信息写入可执行文件(rpath代表runtime path)。这样就不需要设置环境变量。坏处是,如果库文件移动位置,我们需要重新编译test。

更多的参数,更详细的使用说明可以查看 官方手册,或者使用 man info –help 等方式获取。

搜索顺序

测试环境:

1
2
3
4
5
6
7
8
9
vimer@debian8light:~/code/test_search$ uname -a
Linux debian8light 3.16.0-4-amd64 #1 SMP Debian 3.16.7-ckt20-1+deb8u4 (2016-02-29) x86_64 GNU/Linux
vimer@debian8light:~/code/test_search$ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description: Debian GNU/Linux 8.3 (jessie)
Release: 8.3
Codename: jessie
vimer@debian8light:~/code/test_search$

在 gcc version 4.9.2 (Debian 4.9.2-10) 环境下实际测试,linux 编译时头文件的搜索路径:

  1. 搜索会从 -I 开始
  2. 再找 /usr/lib/gcc/x86_64-linux-gnu/4.9/include
  3. 再找 /usr/local/include/
  4. 查找 /usr/include/

此测试只是确定以上 4 者相对的先后。测试时未包含更多的潜在的搜索路径,比如 gcc 的环境变量 C_INCLUDE_PATH、CPLUS_INCLUDE_PATH、OBJC_INCLUDE_PATH

两种不类型的头文件 #include<>#include"" 搜索规则:

  1. 使用<>包含的头文件一般会先搜索-I选项后的路径(即用gcc编译时的-I选项,注意是大写),之后就是标准的系统头文件路径。
  2. 而用””号包含的头文件会首先搜索当前的工作目录,之后的搜索路径才是和<>号包含的头文件所搜索的路径一样的路径。

事实上,知道上述提及的搜索路径即可。不必学究式地死记硬背之间的搜索顺序,能有多少意义呢?。

更直接、简单的方式,确认查找头文件时的路径搜索顺序。参考 使用gcc时头文件路径和动态链接库路径, 通过使用 -v 参数看到:

1
2
3
4
5
6
7
8
9
10
11
12
#include "..." search starts here:
#include <...> search starts here:
./include
/usr/include/c++/4.9
/usr/include/x86_64-linux-gnu/c++/4.9
/usr/include/c++/4.9/backward
/usr/lib/gcc/x86_64-linux-gnu/4.9/include
/usr/local/include
/usr/lib/gcc/x86_64-linux-gnu/4.9/include-fixed
/usr/include/x86_64-linux-gnu
/usr/include
End of search list.

在此不再具体测试编译过程中链接库文件时的搜索顺序、运行时动态库的搜索顺序,真正需要用之间的搜索顺序时再写代码测试。

动态链接和静态链接

在“链接阶段”一节中出现了库文件“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 一并使用。

延伸

以下概念当做扩展知识保留下来,如果有兴趣可以展开学习: