基础开发工具--编译器g++/gcc 自动化构建make/Makefile

目录

gcc/g++

为什么要先生成.o再一起生成可执行?

[为什么从源代码变成可执行的程序需要 预处理 编译 汇编 链接四步?](#为什么从源代码变成可执行的程序需要 预处理 编译 汇编 链接四步?)

条件编译的用途

[动静态库 动静态链接](#动静态库 动静态链接)

1.先理解为什么要有库?

[2.如何区分动静态库? ---通过后缀](#2.如何区分动静态库? ---通过后缀)

3.为什么库是这样的名字呢?--------库的命名规则

动静态链接

动静态库

动静态库的对比

在linux中验证

技术上再理解库

但是如果我不想让被人看的我函数的实现呢?

make/Makefile

一.初步认识

有两点注意的

[在上面提到了一直可以被执行 那么还有不被执行的吗? 为什么呢?](#在上面提到了一直可以被执行 那么还有不被执行的吗? 为什么呢?)

那么是make和makefile是怎么知道哪个新哪个旧的呢?

二.我们也可以用下面的类似于宏替换的方式来把之前的内容做一个替换

三.如果有多个文件呢


gcc/g++

gcc和g++都是编译器 区别在于gcc只支持c语言 g++既支持c也支持c++

从源代码变成可执行的程序 需要 预处理 编译 汇编 链接四步

预处理------宏的替换/去注释/条件编译 (#ifdef #ifndef #endef) / 头文件展开等的操作 (这些过程其实就是把文本代码进行了一些改变)

编译------把预处理后的代码转换为汇编代码

汇编------将汇编语言翻译成二进制机器码

链接----将多个目标文件和库文件合并为 可执行程序

使用方式 gcc [ 选项 ] 要编译的文件 [ 选项 ] [ 目标文件]

下面是由一个.c文件经过这四步后生产可执行程序的过程

也可以由.c文件直接生成可执行的程序

如果有多个.c文件 我们一般会先把所有的.c文件全部编译生成.o文件 然后把所有的.o文件一起编译为一个可执行的程序

为什么要先生成.o再一起生成可执行?

1.不是所有的源代码最后都是要形成可执行的程序 如头文件-其实就是.o文件打了个包 如果其他文件包含头文件 在链接的时候会结合生成可执行程序

2.之前我们写的代码最终是生成了一个可执行的程序 但是如果有1000个源文件 要把他们分别形成多个可执行程序的话 此时只需要先把他们分别都编译形成.o文件 然后再分别多个.o文件直接生成对应的可执行程序

并且之前在vs下写的代码文件里面也可以看到都有一个.obj的文件其实就是对应着linux中的.o文件 也是先会把源文件生成.o的文件

为什么从源代码变成可执行的程序需要 预处理 编译 汇编 链接四步?

这和语言发展的顺序有关

1. 计算机只能识别二进制的0和1 所以最初使用到的编程方式是打孔编程(给计算机传纸条 纸条上有孔 通过光敏的机器来控制 有光无光的方式来表示0和1)

2.后来发现这种方式太容易出错很麻烦也很难理解 后来有了汇编语言 此时我们就需要编译器用来把汇编语言翻译为机器所能识别的二进制机器码 这个编译器是由二进制机器码写的 之后程序员就可以用汇编语言来写代码了 然后通过编译器来把汇编语言翻译成机器码

编译器的自举 : 上面提到这个把汇编语言翻译成机器码的编译器是由机器码写的 是因为当时只有机器码 而通过这个编译器实现汇编语言后 此时这个编译器就可以由汇编语言来实现了 这个编译器由汇编语言实现 作用是把汇编语言转换为机器码

逻辑是先有汇编语言这个想法 然后通过现有的机器码实现编译器 汇编语言就真正实现了 在汇编语言实现之后 此时这个编译器就可以通过汇编语言来实现了 其实最底层还是通过机器码实现的

然后后面又有了c语言 由同样的逻辑慢慢形成了c语言及c语言的编译器

在理解了这个逻辑之后 为什么后面不直接写一个把c语言转换为机器码的编译器呢?

这是因为直接从c到机器码很复杂 而在c语言出现之前 汇编语言已经发展了很久了 从汇编到机器码的翻译已经非常的成熟了 所以有了c后我们只需要考虑把c转换为汇编语言就可以了

条件编译的用途

1.例如一些软件分为普通版和企业版 企业版是收费的 普通版和企业版不是不同的两版 普通版其实就是企业版进过裁剪后的版本 而这个裁剪的方式用到的就是条件编译

2.内核源代码也是根据不同的需求采取条件编译的方式对代码进行裁剪

3.使开发工具或者应用软件可以在不同版本都可以使用 例如有的软件可以在linux使用 有的可以在window使用 有的在这两种系统下都可以使用

动静态库 动静态链接

1.先理解为什么要有库?

假设没有库 那么对于每一个程序员来说想使用类似printf 这样的函数就需要我们自己实现 这样就非常的麻烦 而且每个人设计的都不统一 就像生活中的衣食住行我们想要什么都需要我们自己去手动实现就会非常的麻烦 且一些应该统一的地方每个人设计的不同可能会使社会混乱

所以库的存在是非常有必要 库是一种方法数据集 为我们开发提供了最基本的保证(基本接口.功能) 加速了我们二次开发

库分为动态库和静态库

2.如何区分动静态库? ---通过后缀

动态库 : 在linux下的后缀为.so 在windows下为.dll

静态库 : 在linux下为.a 在windows下为.lib

例如linux下c的动态库为libc.so c的静态库为ibc.a

3.为什么库是这样的名字呢?--------库的命名规则

linux下 lib+库名称.so(a) 也就是实际上库的名称要把前面的lib及后缀去掉

动静态链接

静态链接

编译阶段 就会直接把静态库中所依赖的内容直接复制一份到程序里面 在之后使用的时候就直接使用拷贝过来的内容 如果是多个程序需要拷贝多份

动态链接

运行时 通过动态链接器加载依赖的共享库 程序仅记录库的引用 实际代码在运行时加载 在使用的时候会跳转到对应的库中执行相应的函数 在执行之后会跳转回来

动静态库

库本质就是.o文件打包后形成

静态库在编译时被直接合并到可执行文件中。程序运行时,无需依赖外部库文件。

动态库在程序运行时由动态链接器加载。程序仅记录对库函数的引用,实际代码在运行时解析。

动静态库的对比

1.静态库在编译时进行链接 成为可执行文件的一部分 动态库在运行时进行链接

2.因为静态库需要成为可执行文件的一部分 静态库的程序体积更大

3.可执行程序对静态库的依赖度小 高度依赖动态库如果库缺失或者版本不兼容程序无法正常运行

4.程序运行需要加载到内存 静态链接的会在内存中出现大量的重复代码

5.动态链接比较节省内存和磁盘资源

在linux中验证

ldd 查看可执行程序依赖哪些动态库

用ldd查看linux下的指令依赖的库发现也有c库 也证明了linux下的指令其实就是用c语言写的

在linux中链接时候 默认动态链接

如果想控制静态链接需要在后面加-static不过需要安装有对应的静态库 而默认并没有安装c的静态库 需要我们自己安装 系统默认有动态的没有静态的也意味着系统推荐我们使用动态的方式

安装c的静态库后 使用-static的方式静态链接后 使用file可以观察到所依赖的就是静态链接

如下可以发现 用静态编译的方式文件大小明显大于动态编译的文件大小

在测试c++文件同样如此 静态链接的方式程序大小明显大于静态链接程序大小

技术上再理解库

但是如果我不想让被人看的我函数的实现呢?

如下 只有编译后的.o文件和头文件 却可以正常的编译出可执行程序 而只有.h和.o文件 别人在.h文件只能看到函数的声明 .o文件已经是二进制机器码了 别人看不出内容 此时别人就无法看到我们函数实现的逻辑了

所以我们只要提前把.c文件编译为.o文件 然后把.o文件和头文件传过去就可以让别人看不到我们函数的实现了

1.所以动静态库的本质就可以理解为很多.o文件打包形成的

2.如下 可以看到有很多的.h文件和很多的库(.o文件打包形成的) 所以我们之前写的一个main.c文件 其实就是先编译为.o文件 然后和已有的打包成库的.o进行链接 就形成了可执行的程序 所以链接的本质就是把.o文件合并

make/Makefile

一.初步认识

Makefile是 Unix/Linux 系统下 用于自动化构建项目的工具,通过定义规则和依赖关系,实现代码的编译、链接和清理等操作。它通常与make命令配合使用,能够显著提高开发效率 ,尤其是在大型项目中。 简单来说 make是一个指令 Makefiel是一个文件

如下在Makefile文件中写了如下的代码后 依赖关系开始需要 是用TAB键开始(自动空出四个字符)

在命令行中直接输入make就会直接生成code程序了 相当于执行了上图第二行的依赖关系由mycode.c文件通过gcc生成了code可执行程序

类似于上面 在里面写一个clean 使用时候就会把code给删除了

有两点注意的

1.直接make 会在Makefile文件中从上往下扫描 默认形成第一个目标文件

例如对于上面来说第一个目标文件是code 直接make就会生成code 而使用make clean才会进行删除

如下 如果把他们调换了顺序 直接make默认的就是clean 使用生成code的就需要make code了

2. . PHONY

.PHONY是一个特殊目标 ,用于声明伪目标 。它的作用是告诉make命令---某个目标不是真实的文件 而是一个逻辑上的任务名称 所以这个伪目标就可以一直执行-对应的依赖关系和方法



在上面提到了一直可以被执行 那么还有不被执行的吗? 为什么呢?

确实有不被执行的 默认老代码不会被重新执行

如下make生成code后 再次make就会提示告诉我们 此时这个code是最新的了已经 而code的所依赖文件mycode.c相对于code来说是旧的 所以就不会被重新执行

那么是make和makefile是怎么知道哪个新哪个旧的呢?

在之前指令的学习中学习到过 stat会显示文件最近被更改的时间 而这个时间有三种

Access Modify Change

文件=内容+属性

①文件的内容改变了 Modify会改变

②文件的属性改变了 Change会改变

③查看了文件后 Access被更新

Modify被更新 Change也会同步被更新 因为文件内容改变了 文件的大小size也会发生变换甚至Modify本身也算属性 Modify被更新了 Change也会被同步被更新

但 Change被更新 Modify不一定更新 例如权限修改之后 Change被更新了 而Modify不会被更新

查看文件后Access被更新 但是可以发现继续多次进行cat stat这一行为后 Access并没有被进行更新

这是为什么呢?

因为我们查看一个文件绝大多数情况只是单纯地看 少部分会进行修改 而对于Access的更新维护也是需要成本的 所有只有查看一定的次数后 Access才会进行更新 并不是实时更新

回到正题上

make Makefile中判断目标释放被执行 就是通过检查Modify(文件的内容) 而每个文件都有Modify

可以通过下面时间轴来理解 如下mycode.c第一次被执行后生成code 此时code相对于mycode.c是新的 此时再次make就不能再次执行

但是 在vim mycode.c 在把里面的内容修改之后 此时mycode.c相对于code是更新的了 就可以再次make

在之前指令学过的touch除了可以创建普通文件外 还可以更新文件的时间 所以也可以通过touch更新Modify来再次让make可被执行

所以**.PHONY:就是告诉编译器不要考虑修改时间的问题 直接编译就好了**

而我们一般可编译的程序不会用PHONY修饰 如果这个程序很大的话 编译也是有消耗的 可执行的程序如果没有进行更新我们就不期望对其重新编译

二.我们也可以用下面的类似于宏替换的方式来把之前的内容做一个替换

下面的test是为了验证可行性

例如

MIN=0 就类似于c宏替换的 define MIN 0

在使用的时候 $ (MIN) 才代表着 0

test测试用echo输出会把echo也打印出来 我们不期望它也显示的话 可以在前面加个@

如下就是用这种方式替换为的样子 实际上和之前是完全一样的

并且其实可以用**(@)来替换目标文件 (^)来替换依赖文件 当所依赖文件很多时候就可以体现它的价值 如后面**

使用make时候 会默认时候提示一些信息

我们可以用@把之前的屏蔽掉 然后通过echo输出我们自己想要的提示信息

三.如果有多个文件呢

如果有多个.c文件呢

之前提到 我们期望先把所有的.c编译为.o文件 然后再进行链接生成可执行的程序 所以这里我们也把Makefile修改成这样

下面.c文件生成.o文件可以让所有的.c文件生产对应的.o文件

此时还有两个地方需要修改

1.由.o生成可执行程序可能是由多个.o一起生成一个可执行程序的

2.clean的时候 要一次删除所有的.o文件

通过下面的方式可以解决

此时我们通过make就可以一次性把所有的.c文件先编译成.o文件 然后再一起生成一个可执行的程序

make clean就可以一下把所有的.o文件和最后生成的程序给删除

接下来测试一下

此时有如下 一共12个.c文件

make之后的结果

ll查看确实都生成了

make的结果 确实所有的.o文件及可执行程序都被删除了

所有有了这样的Makefile 之后我们就可以通过make一次性的把所有的.c文件编译成.o文件再生成一个可执行的程序 make clean就可以一次性的把所有的.o文件和可执行的程序删除掉 给我们带来了很大的便利

相关推荐
小白学大数据7 小时前
集成Scrapy与异步库:Scrapy+Playwright自动化爬取动态内容
运维·爬虫·scrapy·自动化
程序员陆通7 小时前
CentOS/AlmaLinux 9 中 SSH 服务启动失败:OpenSSL 版本不匹配解决
linux·centos·ssh
ZYMFZ7 小时前
HAProxy 简介及配置
linux·负载均衡·haproxy
敲上瘾7 小时前
Linux系统C++开发环境搭建工具(三)—— brpc使用指南
linux·c++·分布式·rpc
_dindong7 小时前
牛客101:递归/回溯
数据结构·c++·笔记·学习·算法·leetcode·深度优先
李少兄7 小时前
解决 CentOS 8 报错:Failed to download metadata for repo ‘BaseOS‘
linux·运维·centos
杜子不疼.7 小时前
Linux】 性能调优实战:内核参数优化技巧
linux·运维·php
墨寒博客栈8 小时前
Linux基础常用命令
java·linux·运维·服务器·前端
重生之我在20年代敲代码8 小时前
【Linux网络编程】初识网络,理解TCP/IP五层模型
linux·运维·服务器·网络