要学好 C 语言 / C++ ,Makefile 可少不了

1、Makefile 简介

1. Makefile 是什么?

Makefile 一般指的是一个含有一系列命令(directive)的,经过 Make 自动化编译工具,帮助 C/C++ 程序实现自动编译目标文件的文件。这个文件的默认命名是 "Makefile"。html

2. 为何要使用 Makefile?

Makefile 文件描述了整个工程的编译、连接的规则。linux

为工程编写 Makefile 的好处是可以使用一行命令来完成“自动化编译”。只需提供一个(一般对于一个工程来讲会是多个)正确的 Makefile,接下来每次的编译都只须要在终端输入“make”命令,整个工程便会彻底自动编译,极大提升了效率。尤为是在编译一个仅有一小部分文件被改动过的大项目的状况下。函数

绝大多数的 IDE 开发环境都会为用户自动编写 Makefile。工具

3. Make 是怎么工做的?

Make 工做的原则就是:学习

一个目标文件当且仅当在其依赖文件(dependencies)的更改时间戳比该目标文件的建立时间戳新时,这个目标文件才须要被从新编译。测试

Make 工具会遍历全部的依赖文件,而且把它们对应的目标文件进行更新。编译的命令和这些目标文件及它们对应的依赖文件的关系则所有储存在 Makefile 中。3d

Makefile 中也指定了应该如何建立,建立出怎么样的目标文件和可执行文件等信息。code

除此以外,你甚至还能够在 Makefile 中储存一些你想调用的系统终端的命令,像一个 Shell 脚本同样使用它。htm

2、简单了解编译链接与执行

1. 实验介绍

按照 GNU make 官方手册中采用的教学模式,在正式的学习 Makefile 知识以前,本次实验先介绍一些简单的前导知识。实验详细介绍了 GNU GCC 编译和连接的基本方法,经过编译、连接、静态连接、动态连接的实验内容让用户学习和理解 GCC 的基本使用方法。同时,用户也将在实验过程当中体会到手动编译连接的低效,从而体会到自动编译的在项目工程管理中的重要性。对象

知识点

  • GCC 编译的使用方式
  • GCC 连接的使用方式
  • GCC 静态连接的使用方式
  • GCC 动态连接的使用方式
  • GCC 静态连接 + 动态连接混用的方式

代码获取

经过在 Terminal 中输入如下命令能够将本课程所涉及到的全部源代码下载到在线环境中,做为参照对比进行学习。

wget http://labfile.oss.aliyuncs.com/courses/849/make_example-master.zip && unzip make_example-master.zip && rm make_example-master.zip

命令执行后 WebIDE 的工做区中将会出现一个名为 make_example-master 的文件夹,文件夹中包含了课程所涉及到的源代码,目录结构如图所示:

2. 实验步骤

本章节的源代码位于 /home/project/make_example-master/chapter0 目录中,请在 Terminal 中经过 cd 命令切换至该目录后再进行实验学习。

项目涉及到的代码文件:
(1)main.c: 主要文件
(2)add_minus.c add_minus.h: 加减法 API 及实现
(3)multi_div.c multi_div.h: 乘除法 API 及实现

项目涉及到的 gcc 参数:

参数 描述
-c 编译、汇编指定的源文件(也就是编译源文件),可是不进行连接
-o 用来指定输出文件
-L 为 gcc 增长一个搜索连接库的目录
-l 用来指定程序要连接的库

这一章节咱们将正式开始进行简易四则预算程序的编译实验,分步骤进行。

主程序的编译、连接与执行

打开 chapter0 文件夹查看 main.c 文件,内容以下:

#include <stdio.h>

int main(void)
{
    printf("Hello Cacu!\n");
    return 0;
}

点击 chapter0 文件夹并右键选择 Open in Terminal 在终端中打开 main.c 所在的文件夹。

在 Terminal 中执行如下命令,对 main.c 文件只编译而不连接。

gcc -c main.c

能够发如今当前目录中生成了一个新的文件 main.o

经过 file 命令查看 main.o 的文件格式:

file main.o

输出结果如图所示:

这说明 main.o 其实是一个 relocatable object 文件。

经过如下命令为 main.o 文件赋予可执行的权限:

chmod 777 main.o

chmod 命令用于改变文件的读写以及运行许可设置,详细解绍参考 Permissions

输入如下命令尝试执行 main.o 文件:

./main.o

Terminal 输出可执行文件格式错误,如图所示:

说明 relocatable object 文件是不可执行的。

接下来经过 GCC 对 main.o 文件进行连接操做,从而生成一个可执行的程序 main

在 Terminal 中输入如下命令将 main.o 连接为 main 文件:

gcc -o main main.o

能够发现当前目录新增了一个名为 main 的文件。

经过 file 命令查看 main 的文件格式:

file main

输出结果如图所示:

说明 main 文件是一个可执行的文件,因而经过如下命令来执行 main 文件:

./main

输出结果如图所示:

说明程序获得了正确的执行。

静态连接

编写 add_minus.h 文件,在文件中对函数 add()minus() 进行声明,不过在 chapter0 文件夹中已经提供编写好的 add_minus.h 文件,咱们能够拿来直接使用。

文件内容以下:

#ifndef __ADD_MINUS_H__
#define __ADD_MINUS_H__

int add(int a, int b);
int minus(int a, int b);

#endif /*__ADD_MINUS_H__*

编写 add_minus.c 文件,实现函数 add()minus() ,一样的在 chapter0 文件夹中已经有编写好的 add_minus.c 文件,咱们能够拿来直接使用。

文件内容以下:

#include "add_minus.h"

int add(int a, int b)
{
    return a+b;
}

int minus(int a, int b)
{
    return a-b;

add_minus.c 文件进行编译,生成 add_minus.o 文件。

gcc -c add_minus.c

修改 main.c 文件,为其增长加减法运算并编译这个文件。

执行如下命令给 main.c 打上 v1.0.patch 补丁:

patch -p2 < v1.0.patch

patch 命令能够处理 diff 程序生成的补丁文件,补丁格式能够是四种比较格式中任意一种, 而后把这些差别融入到原始文件中,生成一个打过补丁的版本。-p 选项表示剥离层级,经过在 Terminal 中输入 man patch 命令可获取详细说明。

此时 main.c 文件内容以下:

#include <stdio.h>
#include "add_minus.h"

int main(void)
{
    int rst;

    printf("Hello Cacu!\n");

    rst = add(3,2);
    printf("3 + 2 = %d\n",rst);

    rst = minus(3,2);
    printf("3 - 2 = %d\n",rst);

    return

经过如下命令对 main.c 文件进行编译和连接:

gcc -c main.c
gcc -o main main.o

连接生成的 main.o 文件时,发现有错误出现,错误内容如图所示:

缘由在于连接过程当中找不到 addminus 这两个 symbol。

现将 main.oadd_minus.o 连接成可执行文件并执行测试。

gcc -o main main.o add_minus.o

执行新生成的可执行文件 main

./main

输出结果以下:
说明程序获得了正常执行。

从新编译 add_minus.c 生成 add_minus.o 文件。

gcc -c add_minus.c

经过 ar 命令将 add_minus.o 打包到静态库中。

ar rc libadd_minus.a add_minus.o

能够发如今当前目录下,生成了一个名为 libadd_minus.a 的静态库文件。
file 命令查看 libadd_minus.a 的文件格式。

file libadd_minus.a

Terminal 输出结果如图所示:
实际上 libxxx.a 格式的文件能够简单的当作指定的以 .o 结尾的文件集合。

连接 main.o 和静态库文件。

gcc -o main2 main.o -L./ -ladd_minus

-L./:代表库文件位置在当前文件夹。
-ladd_minus:表示连接 libadd_minus.a 文件,使用 -l 参数时,前缀 lib 和后缀 .a 是须要省略的。

执行 main2

./main2

Terminal 输出结果如图所示:
说明程序的到了正确的执行。

动态连接

编写 multi_div.h 文件,并在其中对函数 multi()div() 进行声明。因为提供的源代码中已经包含了编写好的 multi_div.h 文件,所以咱们能够拿来直接使用。

multi_div.h 文件的内容以下:

#ifndef __MULTI_DIV_H__
#define __MULTI_DIV_H__

int multi(int a, int b);
int div(int a, int b);

#endif /*__MULTI_DIV_H__*

编写 multi_div.c 文件,实现函数 multi()div(),一样的因为提供的源代码中已包含了编写好的 multi_div.c 文件,咱们能够直接拿来使用。

multi_div.c 文件内容以下:

#include "multi_div.h"

int multi(int a, int b)
{
    return a*b;
}

int div(int a, int b)
{
    return a/b;

经过如下命令将 multi_div.c 文件编译成动态连接库。

gcc multi_div.c -fPIC -shared -o libmulti_div.so

-fPIC 选项做用于编译阶段,在生成目标文件时就得使用该选项,以生成位置无关的代码。

命令执行结束后,在当前目录下会生成一个名为 libmulti_div.so 的文件。
经过 file 命令来查看 libmulti_div.so 的文件格式。

file libmulti_div.so

Terminal 输出结果如图所示:
由此可知 libmulti_div.so 是一个 shared object 文件。

删除以前的 main.c 文件,并编写新的 main.c 文件,内容以下:

#include <stdio.h>

int main(void)
{
    printf("Hello Cacu!\n");
    return 0;
}

经过如下命令为 main.c 打上 v2.0.patch 补丁:

patch -p2 < v2.0.patch

此时 main.c 文件的内容以下:

#include <stdio.h>
/*
#include "add_minus.h"
*/
#include "multi_div.h"

int main(void)
{
    int rst;

    printf("Hello Cacu!\n");
/*
    rst = add(3,2);
    printf("3 + 2 = %d\n",rst);

    rst = minus(3,2);
    printf("3 - 2 = %d\n",rst);
*/
    rst = multi(3,2);
    printf("3 * 2 = %d\n",rst);

    rst = div(6,2);
    printf("6 / 2 = %d\n",rst);

    return

编译 main.c 生成 main.o

gcc -c main.c

连接 main.o 与动态连接库文件。

gcc -o main3 main.o -L./ -lmulti_div

执行生成的 main3 文件。

./main3

输出结果出现错误,如图所示:
出现错误的缘由是咱们生成的动态库 libmulti_div.so 并不在库文件搜索路径中。

解决办法:

  1. libmulti_div.so 拷贝到 /lib//usr/lib/ 文件夹下。
sudo cp libmulti_div.so /usr/lib
  1. LD_LIBRARY_PATH 变量中指定库文件路径,而动态连接库文件存放在 /home/project/make_example-master/chapter0/ 路径下。

因此须要在 Terminal 中执行下面的命令:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/project/make_example-master/chapter0/

如今在 Terminal 中执行下面的命令:

./main3

输出结果如图所示:
说明程序获得了正确的执行。

混合使用静态连接与动态连接

删除旧的 main.c 文件,并编写新的 main.c 文件,内容以下:

#include <stdio.h>

int main(void)
{
    printf("Hello Cacu!\n");
    return 0;
}

为新的 main.c 文件打上 v3.0.patch 补丁。

patch -p2 < v3.0.patch

编译 main.c 生成 main.o

gcc -c main.c

测试执行混用静态连接和动态连接的方式。

gcc -o main4 main.o -L./ -ladd_minus -lmulti_div

因为咱们以前已经修改过 LD_LIBRARY_PATH 变量,因此这次无需再次修改。

执行下面的命令:

./main4

输出结果如图所示:
说明程序获得正确的执行。

尽管咱们知道不管是静态连接仍是动态连接都能达到连接对象文件生成可执行文件的目的,可是咱们仍是得 z 注意静态连接库与动态连接库之间的区别,详细内容参考 Static, Shared Dynamic and Loadable Linux Libraries

3、总结

上述内容来自课程《Makefile 基础入门实战》,主要介绍了 GCC 编译,连接的方法和静态连接库与动态连接库的建立和使用方法。

后续课程内容将学习如下内容:

点击《Makefile 基础入门实战》,便可可学习完整课程!

相关文章
相关标签/搜索