要学好 C 语言 / C++ ,Makefile 可少不了
一、Makefile 简介
1. Makefile 是什么?
Makefile 通常指的是一个含有一系列命令(directive)的,通过 Make 自动化编译工具,帮助 C/C++ 程序实现自动编译目标文件的文件。这个文件的默认命名是 “Makefile”。
2. 为什么要使用 Makefile?
Makefile 文件描述了整个工程的编译、链接的规则。
为工程编写 Makefile 的好处是能够使用一行命令来完成“自动化编译”。只需提供一个(通常对于一个工程来说会是多个)正确的 Makefile,接下来每次的编译都只需要在终端输入“make”命令,整个工程便会完全自动编译,极大提高了效率。尤其是在编译一个仅有一小部分文件被改动过的大项目的情况下。
绝大多数的 IDE 开发环境都会为用户自动编写 Makefile。
3. Make 是怎么工作的?
Make 工作的原则就是:
一个目标文件当且仅当在其依赖文件(dependencies)的更改时间戳比该目标文件的创建时间戳新时,这个目标文件才需要被重新编译。
Make 工具会遍历所有的依赖文件,并且把它们对应的目标文件进行更新。编译的命令和这些目标文件及它们对应的依赖文件的关系则全部储存在 Makefile 中。
Makefile 中也指定了应该如何创建,创建出怎么样的目标文件和可执行文件等信息。
除此之外,你甚至还可以在 Makefile 中储存一些你想调用的系统终端的命令,像一个 Shell 脚本一样使用它。
二、简单了解编译连接与执行
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
文件时,发现有错误出现,错误内容如图所示:
原因在于链接过程中找不到 add
和 minus
这两个 symbol。
现将 main.o
和 add_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
并不在库文件搜索路径中。
解决办法:
- 将
libmulti_div.so
拷贝到/lib/
或/usr/lib/
文件夹下。
sudo cp libmulti_div.so /usr/lib
- 在
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
三、总结
上述内容来自课程《Makefile 基础入门实战》,主要介绍了 GCC 编译,链接的方法和静态链接库与动态链接库的创建和使用方法。
后续课程内容将学习以下内容:
点击《Makefile 基础入门实战》,即可可学习完整课程!
要学好 C 语言 / C++ ,Makefile 可少不了相关推荐
- 话说:学好C语言,走遍天下都不怕
文/上嵌科技(简书作者) 原文链接:http://www.jianshu.com/p/b68eb52ed14b 著作权归作者所有,转载请联系作者获得授权,并标注"简书作者". 学好 ...
- 怎样学好C语言,一个成功人士的心得!
今天,我能够自称是一个混IT的人,并能以此谋生,将来大家能一次谋生,都要感谢两个人:克劳德.香农和约翰.冯.诺依曼,是他们发现了全部的数字化信息,不论是一段程序,一封email,一部电影都是用一连串的 ...
- 如何学好C语言,一个成功人士的心得!
来源URL:http://blog.csdn.net/yxnk/article/details/5976699/ 今天,我能够自称是一个混IT的人,并能以此谋生,将来大家能一次谋生,都要感谢两个人:克 ...
- Lesson0:如何学好C语言以及对自己未来的憧憬
一.前言 首先,以一段代码来开头吧~.~ #include <stdio.h> int main() {printf("Hello World!\n");return ...
- 语言都是相通的,学好一门语言,再学第二门语言就很简单,记录一下我复习c语言的过程。...
语言都是相通的,学好一门语言,再学第二门语言就很简单,记录一下我复习c语言的过程. 为了将本人的python培训提高一个层次,本人最近买了很多算法的书. 这个书上的代码基本都是c语言实现的,c语言很久 ...
- 12天学好C语言——记录我的C语言学习之路(Day 12)
12天学好C语言--记录我的C语言学习之路 Day 12: 进入最后一天的学习,用这样一个程序来综合考量指针和字符串的关系,写完这个程序,你对字符串和指针的理解应该就不错了. //输入一个字符串,内有 ...
- C语言那点事——如何从零学好C语言?
本文的核心,是在讨论如何真正的学好C语言,而不是讨论如何在C语言考试中拿高分.当然真正学好了C语言,拿高分也就不那么难了. C语言是很多大学生大一的一门必修课,尤其是理工科专业,这门课程更是重中之重. ...
- c语言 单词变复数_【热点】浅谈 :怎样学好C语言?
是新朋友吗?记得先点蓝字关注我哦- 本文共 1900 字,预计阅读时间: 7 分钟. 最近有好多朋友和我抱怨说C语言真的太难学了,你有没有什么好的意见?嗯,的确如此,我上个学期学C语言的时候也是这么认 ...
- 在你迷茫时不如学好一门语言(送给大一的学弟学妹)
在你迷茫时不如学好一门语言 个人经历: 记得大一刚来的时候,完全不理解软件专业是干嘛的,就知道跟着老师和一些学长敲一些没见过的代码.刚开始学的是C语言,那些代码都不理解是什么意思,学长说,你不用理解, ...
- 怎样学好c语言程序设计这门,初学C语言程序设计的基本方法和技巧
无论哪所大学的计算机专科和本科都需要学习C语言<,C语言程序设计>是计算机专业的一门必修课程,也是学习如何编程的入门课.初学C语言的人都会觉得C语言不好学,感到无从下手.其实,按照我们现在 ...
最新文章
- 一起学习阿里巴巴数据中台实践!首次公开!
- Github 完整学习教程
- inotify-java下载_inotify-java-2.1.jar
- 什么是Web 2.0——下一代软件的设计模式和商业模式 (全文翻译—1 博客版序)
- TensorFlow 教程 --新手入门--1.3 安装实例
- R every day !
- 关于 try catch 捕捉不到异常
- Python-sorted函数
- Linux操作系统中常用软件包的下载命令
- DFRobot for Arduino 中级套件
- amos里CFA可行性辨别结果怎么看_AMOS 中验证性因素分析(CFA)
- 要用计算机处理频谱,妙用Adobe Audition 系列教程(二):频谱分析仪 | 小众声学...
- HTML星星组成的平行四边形,用一个程序打印菱形,平行四边形星星图
- Win11启动修复无效怎么办
- Arturia Buchla Easel V for Mac - Buchla音乐画架插件
- 卸载Autodesk系列之后电脑屏幕灰白怎么解决
- 计算机绘图图框实验报告,CAD实验报告DOC
- OpenResty实现限流的几种方式
- 技术控必读 从Type-A到Type-C发展历程
- 已知某校有以下老师及教授课程,1) 使用一个Map,以老师的名字作为键,以老师教授的课程名作为值,表示上述 课程安排。