文章目录
Linux软件包管理器 - yum
什么是软件包
- 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序.
但是这样太麻烦了,
- 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安
装程序)放在一个服务器上, 通过包管理器可以很方便的获取到这个编译好的软件包, 直接进行安装.
- 软件包和软件包管理器, 就好比 “App” 和 “应用商店” 这样的关系.
- yum(Yellow dog Updater, Modified)是Linux下非常常用的一种包管理器. 主要应用在Fedora, RedHat,Centos等发行版上.而
apt
则适用于unbuntu系统。
Linux下安装软件的方式
在Linux下安装软件的方法大概有以下三种:
1)下载到程序的源代码,自行进行编译,得到可执行程序。
2)获取rpm安装包,通过rpm命令进行安装。(未解决软件的依赖关系)
3)通过yum进行安装软件。(常用)
apt:
apt
是在 Ubuntu 16.04 及更高版本中引入的一个命令行工具,旨在简化和整合apt-get
和apt-cache
的常用功能,提供更友好的用户界面和更一致的命令语法。apt语法和yum类似,这里就不再详细讲解。****
认识yum
yum是一个在Fedora、RedHat以及CentOS中的前端软件包管理器,能够从指定的服务器自动下载RPM包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软件包,无须繁琐地一次次下载、安装。
yum的工作原理
yum
通过与配置好的软件仓库交互,来管理系统中的软件包。其基本工作流程如下:
- 仓库定义:系统通过配置文件定义多个软件仓库,指定软件包的来源。
- 包信息获取:
yum
从仓库中获取软件包的元数据,包括包的名称、版本、依赖关系等。 - 用户请求:用户通过
yum
命令发出安装、更新或卸载软件包的请求。 - 依赖解析:
yum
自动分析所需的依赖关系,并从仓库中下载相应的软件包。 - 包安装/更新/卸载:
yum
使用 RPM 工具实际执行软件包的安装、更新或卸载操作。 - 缓存管理:
yum
将下载的软件包缓存到本地,以便后续使用。
注意:一个服务器同一时刻只允许一个yum进行安装,不能在同一时刻同时安装多个软件。
因为yum是从服务器上下载RPM包,所以在下载时必须联网,可以通过ping指令判断当前云服务器是否联网。
查找软件包
[cl@VM-0-15-centos lesson5]$ yum list
使用yum list指令,可以罗列出可供下载的全部软件。
说明一下:
1)软件包名称:主版本号.次版本号.源程序发行号-软件包的发行号.主机平台.cpu架构。
2)"x86_64"后缀表示64位系统的安装包,"i686"后缀表示32位系统安装包,选择包时要和系统匹配。
3)"el7"表示操作系统发行版的版本,“el7"表示的是"centos7/redhat7”,“el6"表示"centos6/redhat6”。
4)最后一列表示的是“软件源”的名称,类似于“小米应用商店”,“华为应用商店”这样的概念。
这里我们以查找lrzsz为例。
lrzsz可以将Windows当中的文件上传到Linux当中,也可以将Linux当中的文件下载到Windows当中,实现云服务器和本地机器之间进行信息互传。
yum list | grep lrzsz
由于包的数量非常多,所以我们可以使用grep指令筛选出我们所关注的包,这里我们以lrzsz为例。
此时就只会显示与lrzsz相关的软件包。
安装软件
指令: sudo yum install 软件名
安装lrzsz
sudo yum install lrzsz
安装 vim
编辑器:
sudo yum install vim
yum会自动找到都有哪些软件包需要下载,这时候敲“y”确认安装,当出现“complete”字样时,说明安装完成。
注意事项:
1)安装软件时由于需要向系统目录中写入内容,一般需要sudo或者切换到root账户下才能完成。
2)yum安装软件只能一个装完了再装另一个,正在使用yum安装一个软件的过程中,如果再尝试用yum安装另外一个软件,yum会报错。
更新软件包
更新已安装的软件包到最新版本。
sudo yum update <package_name>
示例:
更新所有可更新的软件包:
sudo yum update
卸载软件包
删除已安装的软件包。
sudo yum remove <package_name>
示例:
卸载 vim
编辑器:
sudo yum remove vim
搜索软件包
在仓库中搜索包含指定关键字的软件包。
yum search <keyword>
示例:
搜索与 editor
相关的软件包:
yum search editor
显示软件包信息
查看指定软件包的详细信息,包括描述、版本、依赖等。
yum info <package_name>
示例:
查看 vim
的信息:
yum info vim
列出可用的软件包
列出仓库中所有可用的软件包。
yum list available
列出已安装的软件包
列出系统中所有已安装的软件包。
yum list installed
清理缓存
清理 yum
下载的缓存,以释放磁盘空间。
sudo yum clean all
如何实现本地机器和云服务器之间的文件互传
既然已经安装了lrzsz,这里就顺便说一下lrzsz如何使用。
指令: rz -E
通过该指令可选择需要从本地机器上传到云服务器的文件。
指令: sz 文件名
该指令可将云服务器上的文件下载到本地机器的指定文件夹。
基础软件源和拓展软件源
基础软件源
在 yum
中,基础软件源通常指的是系统默认启用的官方仓库。这些仓库包含了操作系统的核心组件和常用的软件包。
- 官方仓库:这些仓库是操作系统的主要软件包来源,通常包含操作系统的核心组件以及与操作系统兼容的基础软件包。
- 例如,CentOS、RHEL 和 Fedora 系统会提供官方的仓库,包括基础软件包和维护更新。
默认情况下,基础仓库通常包括:
- base:包含操作系统的核心包和依赖。
- updates:提供操作系统的安全更新和补丁。
- extras:包含一些额外的软件包,不是操作系统的核心部分,但由官方维护。
仓库的配置文件通常位于 /etc/yum.repos.d/
目录下,例如:
/etc/yum.repos.d/CentOS-Base.repo
在该文件中,仓库的配置示例如下:
[base]
name=CentOS-$releasever - Base
baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
enabled=1
gpgcheck=1
这些配置定义了如何从 URL 获取基础软件包。
拓展软件源
拓展软件源通常指的是除了操作系统核心仓库之外的第三方仓库,提供额外的软件包、工具或驱动程序。这些源由社区或其他厂商维护,常见的拓展仓库包括:
- EPEL (Extra Packages for Enterprise Linux):提供 RHEL 和 CentOS 中不包含的额外开源软件包,适用于许多企业环境中需要的额外软件。
- RPMFusion:提供额外的第三方软件包,包括一些专有软件。
通过安装相应的仓库配置包(如 epel-release
)来启用这些拓展源:
yum install -y epel-release
拓展源的配置文件也会在 /etc/yum.repos.d/
目录中生成,类似于:
[epel]
name=Extra Packages for Enterprise Linux $releasever - $basearch
baseurl=https://download.fedoraproject.org/pub/epel/$releasever/$basearch/
enabled=1
gpgcheck=1
Linux编辑器 - vim
vim的基本概念
vi/vim的区别简单点来说,它们都是多模式编辑器,不同的是vim是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面。例如语法加亮,可视化操作不仅可以在终端运行,也可以运行于x window、 mac os、windows。下面我们统一按照vim来进行讲解。
vim在我们做开发的时候,主要解决我们编写代码的问题,本质上就是一个多模式的文本编辑器。
我们这里主要介绍vim最常用的三种模式:命令模式、插入模式、底行模式。
1、命令(正常)模式(Normal mode)。
在命令模式下,我们可以控制屏幕光标的移动,字符、字或行的删除,复制粘贴,剪贴等操作。
2、插入模式(Insert mode)。
只有在插入模式下才能进行文字输入,该模式是我们使用最频繁的编辑模式。按「ESC」键可回到命令行模式。该模式是我们后面用的最频繁的编辑模式。
3、底行(末行)模式(Command mode)。
在底行模式下,我们可以将文件保存或退出,也可以进行查找字符串等操作。在命令模式下,shift+: 即可进入该模式。在底行模式下我们还可以直接输入vim help-modes查看当前vim的所有模式。
vim下各模式的切换
指令: vim 文件名
进入vim后默认为命令模式(普通模式),要输入文字需切换到插入模式。【命令模式】切换至【插入模式】
1)输入「i」:在当前光标处进入插入模式。
2)输入「a」:在当前光标的后一位置进入插入模式。
3)输入「o」:在当前光标处新起一行进入插入模式。
【命令模式】切换至【底行模式】
1)输入「Shift+;」即可,实际上就是输入「:」。
【插入模式】或【底行模式】切换至【命令模式】
1)插入模式或是底行模式切换至命令模式都是直接按一下「Esc」键即可。
vim命令模式各命令汇总
【移动光标】
1)按「k」:光标上移。
2)按「j」:光标下移。
3)按「h」:光标左移。
4)按「l」:光标右移。
5)按「$」:移动到光标所在行的行尾。
6)按「^」:移动到光标所在行的行首。
7)按「gg」:移动到文本开始。
8)按「Shift+g」:移动到文本末尾。
9)按「n+Shift+g」:移动到第n行行首。
10)按「n+Enter」:当前光标向下移动n行。
11)按「w」:光标从左到右,从上到下的跳到下一个字的开头。
12)按「e」:光标从左到右,从上到下的跳到下一个字的结尾。
12)按「b」:光标从右到左,从下到上的跳到上一个字的开头
【删除】
1)按「x」:删除光标所在位置的字符。
2)按「nx」:删除光标所在位置开始往后的n个字符。
3)按「X」:删除光标所在位置的前一个字符。
4)按「nX」:删除光标所在位置的前n个字符。
5)按「dd」:删除光标所在行。
6)按「ndd」:删除光标所在行开始往下的n行。
【复制粘贴】
1)按「yy」:复制光标所在行到缓冲区。
2)按「nyy」:复制光标所在行开始往下的n行到缓冲区。
3)按「yw」:将光标所在位置开始到字尾的字符复制到缓冲区。
4)按「nyw」:将光标所在位置开始往后的n个字复制到缓冲区。
5)按「p」:将已复制的内容在光标的下一行粘贴上。
6)按「np」:将已复制的内容在光标的下一行粘贴n次。
【剪切】
1)按「dd」:剪切光标所在行。
2)按「ndd」:剪切光标所在行开始往下的n行。
3)按「p」:将已剪切的内容在光标的下一行粘贴上。
4)按「np」:将已剪切的内容在光标的下一行粘贴n次。
【撤销】
1)按「u」:撤销。
2)按「Ctrl+r」:恢复刚刚的撤销。
【大小写切换】
1)按「shift+~」:完成光标所在位置字符的大小写切换。
2)按「shift+n~」:完成光标所在位置开始往后的n个字符的大小写切换。
【替换】
1)按「r」:替换光标所在位置的字符。
2)按「R」:替换光标所到位置的字符,直到按下「Esc」键为止。
【更改】
1)按「cw」:将光标所在位置开始到字尾的字符删除,并进入插入模式。
2)按「cnw」:将光标所在位置开始往后的n个字删除,并进入插入模式。
【翻页】
1)按「Ctrl+b」:上翻一页。
2)按「Ctrl+f」:下翻一页。
3)按「Ctrl+u」:上翻半页。
4)按「Ctrl+d」:下翻半页。
vim底行模式各命令汇总
在使用底行模式之前,记住先按「Esc」键确定你已经处于命令模式,再按「:」即可进入底行模式。
【行号设置】
1)「set nu」:显示行号。
2)「set nonu」:取消行号。
【保存退出】
1)「w」:保存文件。
2)「q」:退出vim,如果无法离开vim,可在「q」后面跟一个「!」表示强制退出。
3)「wq」:保存退出。
【分屏指令】
1)「vs 文件名」:实现多文件的编辑。
2)「Ctrl+w+w」:光标在多屏幕下进行切换。
【执行指令】
1)「!+指令」:在不退出vim的情况下,可以在指令前面加上「!」就可以执行Linux的指令,例如查看目录、编译当前代码等。
vim的简单配置
【配置文件的位置】
1)在目录/etc/下面,有个名为vimrc的文件,这是系统中公共的配置文件,对所有用户都有效。
2)在每个用户的主目录/home/xxx下,都可以自己建立私有的配置文件,命名为“.vimrc”,这是该用户私有的配置文件,仅对该用户有效。
例如,普通用户在自己的主目录下建立了“.vimrc”文件后,在文件当中输入set nu指令并保存,下一次打开vim的时候就会自动显示行号。
vim的配置比较复杂,某些vim配置还需要使用插件,建议不要自己一个个去配置。比较简单的方法是直接执行以下指令(想在哪个用户下让vim配置生效,就在哪个用户下执行该指令,不推荐直接在root下执行):
curl -sLf https://gitee.com/HGtz2222/VimForCpp/raw/master/install.sh -o ./install.sh && bash ./install.sh
然后按照提示输入root密码:
长度-
然后等待安装配置,最后手动执行source ~/.bashrc即可。
配置完成后,像什么自动补全、行号显示以及自动缩进什么的就都有了。
Linux编译器 - gcc/g++
gcc/g++的作用
GCC(GNU Compiler Collection)是一个功能强大的编译器,支持多种编程语言,包括 C、C++、Fortran 等。它是 Linux 系统上最常用的编译器之一,广泛用于编译各种开源软件和系统程序。gcc和g++在执行编译的时候一般有以下四个步骤:
1)预处理(头文件展开、去注释、宏替换、条件编译)。
2)编译(C代码翻译成汇编语言)。
3)汇编(汇编代码转为二进制目标代码)。
4)链接(将汇编过程产生的二进制代码进行链接)。
gcc/g++语法
语法:gcc [选项] 源文件 [选项] [目标文件]
例如:
gcc -E hello.c -o hello.i
gcc -S hello.i -o hello.s
gcc -c hello.s -o hello.o
gcc hello.o -o hello
常用选项:
常见预处理选项
-E
:只进行预处理,停止在预处理阶段。这个不生成文件,你需要把他重定向到一个输出文件里面(否则将把预处理后的结果打印到屏幕上)。<font style="color:#DF2A3F;">-D </font>macro=value
:定义宏。例如,<font style="color:#DF2A3F;">-D</font>DEBUG=1
定义了一个宏DEBUG
,其值为 1。-I
:指定额外的头文件搜索路径。例如,-I/usr/local/include
。
编译选项
-S
:将源代码编译为汇编代码,不进行汇编和链接。-c
:将源代码编译为目标文件,不进行链接。-o
:指定输出文件的名称。
优化选项
-O0
:不进行优化(默认)。-O1
:进行基础的优化。-O2
:进行更多优化(较常用)。-O3
:进行最大化优化,可能增加编译时间和二进制文件大小。
调试选项
-g
:生成调试信息,方便使用 GNU 调试器(GDB)进行调试。
警告选项
-w
:禁止所有警告信息。-Wall
:生成所有警告信息,帮助发现潜在的代码问题。
库链接选项
-static
:生成静态链接的可执行文件,即将所有需要的库代码直接包含到可执行文件中。-shared
:生成动态库链接,生成共享库文件(.so
)。
其他选项
-v
:显示编译过程中的详细信息,帮助调试编译问题。-I
:指定头文件的搜索路径。-L
:指定库文件的搜索路径。
预处理
预处理是 GCC 编译过程的第一步,主要负责宏替换、文件包含、条件编译和注释删除等操作。在这个阶段,GCC 会处理所有以 #
开头的预处理指令,如 #define
、#include
等。
- 宏替换:对代码中的宏进行替换。
- 文件包含:将头文件中的内容插入到源文件中。
- 条件编译:根据编译时的条件编译代码片段(如
#ifdef
、#endif
)。 - 删除注释:将源代码中的注释移除。
命令示例:
gcc -E hello.c -o hello.i
<u>-E</u>
:表示只进行预处理,不进行编译。-o hello.i
:指定输出文件为hello.i
,这是经过预处理后的源代码。
编译
编译阶段将经过预处理的源代码(通常是 .i
文件)转换为汇编语言代码。在这一阶段,GCC 会检查代码的语法是否正确,并进行语法分析。
命令示例:
gcc -S hello.i -o hello.s
<u>-S</u>
:将 C 代码编译为汇编语言代码,但不进行汇编和链接。输出为<u>.s</u>
文件。hello.i
:经过预处理的源代码。
汇编
在汇编阶段,GCC 会将汇编语言代码(.s
文件)转化为机器可执行代码。这个过程会生成目标文件(通常是 .o
文件)。
命令示例:
gcc -c hello.s -o hello.o
<u>-c</u>
:将<u>.s</u>
汇编文件编译成<u>.o</u>
目标文件,不进行链接。hello.s
:汇编语言代码文件。hello.o
:目标文件(机器可识别的二进制代码)。
链接
链接阶段将目标文件与库文件(如 C 标准库、第三方库等)合并,生成最终的可执行文件。在这个阶段,系统会将所有的函数引用解析并链接到实际的函数定义。
命令示例:
gcc hello.o -o hello
hello.o
:目标文件。-o hello
:指定输出的可执行文件名为hello
。
在链接阶段,GCC 会查找所有使用的库(如 printf
来自于 libc
),并将它们链接到可执行文件中。
注意: 链接后生成的也是二进制文件。
./命令
在 Linux 中,
./
是一个相对路径的表示方式,它指代当前目录。具体来说,./
表示当前工作目录,通常用于执行当前目录下的可执行文件或脚本。解释:
./a.out
:表示执行当前目录(即/home/lsy/lesson8/
)下的a.out
文件。在 Linux 中,系统通常不会自动将当前目录包含在执行命令时的路径中(为了安全考虑),因此需要使用
./
来指定当前目录并执行其中的程序。- 由于 `a.out` 是 `g++` 编译后的默认输出文件,它是一个可执行文件。为了运行该文件,你需要明确指定文件的路径,因为当前目录(`.`)并不自动包含在系统的 `PATH` 环境变量中。
静态库与动态库
在 Linux 系统中,库文件是编译和链接过程中非常重要的一部分,它们包含了可供程序使用的预编译代码。在开发中,通常会使用静态库和动态库来封装常用的功能和模块。下面我们将详细讲解这两种库的概念、区别、优缺点以及在 Linux 中的使用方式。
1. 什么是静态库(Static Library)
静态库(.a
文件)是在编译时被静态地链接到程序中的库文件。静态库包含了已编译的目标文件,它们会被嵌入到最终生成的可执行文件中。
静态库的创建
静态库的创建通常涉及以下几个步骤:
- 编写源代码(例如
foo.c
):- 这是包含函数实现的源文件。
- 编译源代码为目标文件(
.o
):
gcc -c foo.c -o foo.o
- 使用编译器将源文件编译为目标文件。
- 创建静态库(
.a
** 文件)**:
ar rcs libfoo.a foo.o
- 使用 `ar` 工具将目标文件打包成静态库。
- `libfoo.a` 就是静态库文件,它将包含 `foo.o` 中的所有函数和数据。
静态库的链接
在链接时,静态库的内容会被直接嵌入到最终生成的可执行文件中。例如,使用静态库时的编译命令如下:
gcc main.c -L. -lfoo -o main
这里:
main.c
是用户的源代码文件。-L.
指定库文件的路径。-lfoo
表示链接libfoo.a
库文件。main
是最终生成的可执行文件。
静态库的特点
- 编译时链接:静态库在编译时被链接到程序中,程序的最终可执行文件包含了库的所有代码。
- 独立性:最终的可执行文件不依赖于静态库,因此即使静态库文件被删除或更改,程序依然能够运行。
- 文件增大:静态库的代码会被嵌入到每个使用它的可执行文件中,可能导致可执行文件的体积增大。
2. 什么是动态库(Dynamic Library)
动态库(.so
文件,shared object)是在程序运行时被动态加载和链接的库文件。与静态库不同,动态库在编译时并不会被嵌入到程序中,而是在程序运行时由操作系统加载。
动态库的创建
- 编写源代码(例如
foo.c
):- 同样是包含函数实现的源文件。
- 编译源代码为共享目标文件(
.o
):
gcc -fPIC -c foo.c -o foo.o
- 使用 `-fPIC`(Position Independent Code)选项生成位置无关代码,以支持动态链接。
- 创建动态库(
.so
** 文件)**:
gcc -shared -o libfoo.so foo.o
这样就创建了名为 libfoo.so
的动态库文件。
- 使用 `gcc` 将目标文件打包成动态库。
动态库的链接
- gcc的默认链接方式
在编译时,程序只需要知道动态库的名称,而不需要将库文件包含在可执行文件中。例如,编译时使用动态库的命令如下:
gcc main.c -L. -lfoo -o main
在运行时,操作系统会查找并加载 libfoo.so
文件。动态库通常放置在系统的库目录下(如 /usr/lib
)或指定的目录(通过 -L
参数)。
动态库的特点
- 运行时链接:动态库在程序运行时被加载,系统会在运行时将动态库的地址映射到程序的内存空间中。
- 节省内存:多个程序可以共享同一个动态库的内存映像,节省了内存资源。
- 可更新:动态库可以在不重新编译程序的情况下更新,只要库的接口没有改变,程序就可以使用新版本的库。
- 依赖性:程序在运行时需要依赖动态库,如果库文件丢失或版本不兼容,程序可能无法运行。
3. 静态库和动态库的区别
特性 | 静态库(Static Library) | 动态库(Dynamic Library) |
---|---|---|
文件扩展名 | .a | .so |
编译时/运行时 | 编译时链接 | 运行时链接 |
代码链接 | 静态库的代码会被嵌入到每个可执行文件中 | 程序运行时动态加载并链接到内存中 |
共享性 | 每个程序都包含库的拷贝,不能共享内存资源 | 多个程序可以共享同一份库的内存映像,节省内存资源 |
更新 | 更新静态库时需要重新编译所有依赖该库的程序 | 更新动态库后,不需要重新编译程序,只要接口不变即可 |
生成的可执行文件大小 | 生成的可执行文件较大,因为库的代码被嵌入其中 | 可执行文件较小,因为库的代码并不嵌入程序中,而是依赖运行时加载 |
依赖性 | 不依赖外部库,库代码已经嵌入可执行文件中 | 依赖外部动态库文件,运行时必须能够找到对应的 .so 文件 |
4. 静态库和动态库的优缺点
静态库的优缺点
优点:
- 不依赖外部库:
- 静态库是完全自包含的,程序在编译时就将静态库的代码链接到可执行文件中。因此,不存在运行时找不到库文件的问题,程序不依赖于外部库。
- 程序的可移植性较强,在没有相应库的环境下也能直接运行,只要该程序和静态库的兼容性没有问题。
- 程序不受库文件影响:
- 静态库不会受到动态库版本变动的影响,不必担心因动态库版本的更新而导致的兼容性问题。程序运行时完全不依赖外部库文件,减少了因库文件丢失或版本不一致造成的错误。
- 在同一平台上直接运行:
- 静态库生成的可执行文件不依赖于任何外部的库文件,因此它在同一平台上可以直接运行,不需要依赖系统中安装的共享库版本。对于某些没有安装特定库文件的环境,这点尤其重要。
缺点:
- 可执行文件较大:
- 由于静态库在编译时被嵌入到程序中,每个使用静态库的程序都会包含一份该库的代码。因此,最终生成的可执行文件会比使用动态库时要大得多。
- 如果有多个程序使用相同的静态库,每个程序都会包含一份该库,导致磁盘空间的浪费。
- 资源浪费:
- 内存资源浪费:每个程序都需要加载自己独立的静态库副本。如果多个程序使用相同的静态库,它们每个都需要将该库加载到内存中,这就浪费了内存资源。
- 网络资源浪费:如果静态库较大,每个程序都包含一份,可能会导致网络传输时的浪费,尤其是在分发程序时。
- 更新和维护不方便:
- 如果静态库有更新或者修复了 bug,程序需要重新编译才能使用新的版本。这与动态库不同,动态库更新后程序可以不需要重新编译就能自动使用新版本的库。
动态库的优缺点
优点:
- 节省资源:
- 磁盘资源:动态库通常只存在一份,无论多少个程序使用它,都不需要为每个程序复制一份库文件。这样可以有效节省磁盘空间。
- 内存资源:动态库是按需加载的,同一个动态库如果被多个程序使用,操作系统可以将其加载到内存中并共享,这样可以避免不同程序加载相同的代码,节省内存。
- 网络资源:如果动态库存放在网络服务器上,程序可以按需从远程服务器加载动态库,避免了将整个程序库文件本地存储的资源浪费。
- 升级和维护方便:
- 动态库是独立的,可以单独升级,而不需要重新编译使用该库的应用程序。只要接口保持不变,程序可以直接使用更新后的库,这样可以避免每次更新都要重新编译程序。
- 减少重复代码:
- 在多个程序中共用同一个动态库,不必为每个程序复制库文件,可以减少代码的重复,避免多个程序加载相同的库文件。
缺点:
- 对库的依赖性强:
- 动态库的主要缺点之一是它对库文件的依赖性很强。如果库文件丢失或版本不兼容,程序将无法正常运行。这个问题通常称为“DLL 地狱”或“库依赖问题”。
- 当操作系统或程序的库文件路径发生变化,或者库文件版本不同,也可能导致程序无法找到正确的动态库,从而导致程序崩溃或无法启动。
- 运行时加载的开销:
- 虽然动态库在内存中共享,但在程序启动时,系统需要加载和链接动态库,这会比静态库多出一些运行时的开销。
- 在某些情况下,动态库的加载和解析可能会影响程序的启动时间。
gcc和g++默认生成的二进制程序是动态链接的,我们可以使用file指令进行查看。
其次,我们还可以使用ldd指令查看动态链接的可执行文件所依赖的库。
(图中的/lib64/libc.so.6就是当前云服务器当中的C标准库)。
虽然gcc和g++默认采用的是动态链接,但如果我们需要使用静态链接,带上-static选项即可。
[cl@VM-0-15-centos lesson9]$ gcc test.c -o test_s -static
此时生成的可执行文件就是静态链接的了。
我们可以查看源代码相同,但链接方式不同而生成的两个可执行程序test和test_s的大小。
这也证明了动态链接比较节省空间,而静态链接比较浪费空间。
条件编译以及使用场景
条件编译与宏定义(-D macro=value
)详解
在 C 和 C++ 编程中,条件编译是一种非常有用的技术,它允许在编译时根据特定的条件(如宏定义的值)来包含或排除代码的某些部分。这样,我们可以在同一份源代码中为不同的场景或平台做出不同的代码选择,而不必维护多份代码。
1. 什么是条件编译?
条件编译是指根据某些条件来选择性地编译代码的技术。在预处理阶段,编译器会根据这些条件判断是否要包含特定的代码块。这些条件通常通过宏定义来控制,常用的指令有 #if
、#ifdef
、#ifndef
、#else
和 #endif
。
-D macro=value
是 GCC 编译器中的一个选项,它用于在编译时定义宏。宏的定义可以是简单的名称,也可以带有值。这个宏可以在程序中用于条件编译,控制哪些代码段被编译。
2. -D macro=value
的作用
-D macro=value
选项用于在编译时定义宏,宏可以在源代码中使用。在条件编译中,宏的定义用于决定是否编译某些代码块。
语法:
gcc -DDEBUG=1 source.c -o output
这条命令定义了一个名为 DEBUG
的宏,并将其值设为 1
。在代码中,我们可以通过 #ifdef DEBUG
来判断 DEBUG
是否已经定义,从而决定是否编译某些代码块。
示例:
#include <stdio.h>
int main() {
#ifdef DEBUG
printf("Debug mode is ON\n");
#else
printf("Release mode is ON\n");
#endif
return 0;
}
如果在编译时使用 -DDEBUG=1
,那么程序会输出 "Debug mode is ON"
,因为 DEBUG
被定义了。如果没有定义 DEBUG
或者定义了 -DDEBUG=0
,则输出 "Release mode is ON"
。
3. 经典的使用场景:
条件编译通常用于以下场景:
- 调试与发布模式区分:在开发和调试时,常常需要输出调试信息(如
printf
或log
),而在发布版本中则去除这些调试信息。 - 不同平台的适配:在跨平台开发时,某些代码可能只适用于特定平台,如 Windows 和 Linux。通过条件编译,可以根据不同的平台宏来决定编译哪些代码。
- 功能开关:通过宏定义,可以启用或禁用某些功能,比如某个模块的测试或新特性。
- 使用条件编译的经典示例:
示例:调试与发布版本的代码分离
假设你在编写一个程序时,需要在调试版本中输出大量的调试信息,而在发布版本中则不希望输出这些信息。此时,可以使用条件编译来控制这些调试输出。
#include <stdio.h>
// 使用 -DDEBUG=1 编译时启用调试信息
#ifdef DEBUG
#define LOG(msg) printf("DEBUG: %s\n", msg)
#else
#define LOG(msg) // 空宏,什么都不做
#endif
int main() {
LOG("This is a debug message");
printf("This is a normal message\n");
return 0;
}
- 在开发时,使用
gcc -DDEBUG=1
编译,这样调试信息会被输出。 - 在发布时,使用
gcc
编译而不定义DEBUG
,调试信息将不会输出。
示例:不同版本的代码(如社区版和专业版)
如你所提到的 Visual Studio 2019 会有社区版和专业版的区别,后台人员可能在维护这两个版本的代码时,使用条件编译来确保不同版本的代码分别执行不同的功能或包含不同的资源。
例如,假设你有一个代码库,其中一些功能只在专业版中启用,而社区版则不需要这些功能。你可以通过条件编译来实现:
#include <stdio.h>
// 使用 -DPROFESSIONAL_VERSION=1 编译时启用专业版功能
#ifdef PROFESSIONAL_VERSION
#define FEATURE_X_ENABLED
#define FEATURE_Y_ENABLED
#else
#define FEATURE_X_ENABLED // 社区版不启用
#endif
int main() {
printf("Starting application...\n");
#ifdef FEATURE_X_ENABLED
printf("Feature X is enabled!\n");
#endif
#ifdef FEATURE_Y_ENABLED
printf("Feature Y is enabled!\n");
#endif
return 0;
}
- 在社区版编译时,
-DPROFESSIONAL_VERSION=1
这一宏不被定义,FEATURE_X_ENABLED
和FEATURE_Y_ENABLED
都不会启用。 - 在专业版编译时,使用
gcc -DPROFESSIONAL_VERSION=1
编译,程序会启用FEATURE_X_ENABLED
和FEATURE_Y_ENABLED
,相应的功能会被编译进最终的程序。
5. 其他常见的宏定义
- 平台相关宏:在跨平台开发中,不同平台的头文件和实现可能有所不同,常常使用条件编译来处理。例如:
#ifdef _WIN32
// Windows-specific code
#elif defined(__linux__)
// Linux-specific code
#endif
- 编译器相关宏:不同的编译器可能有不同的特性,可以使用宏来针对特定编译器进行编译时优化或修正:
#ifdef __GNUC__
// GCC specific code
#endif
6. 总结
-D macro=value
选项是 GCC 编译器中非常有用的功能,它通过定义宏来控制代码的编译。条件编译使得我们能够在同一份代码中,针对不同的环境、平台、版本或功能开关来动态选择编译内容。典型的使用场景包括调试信息的控制、不同平台或版本的适配以及功能模块的启用或禁用等。在大型项目中,合理使用条件编译可以大大提高代码的可维护性和灵活性。
Linux调试器 - gdb
gdb使用须知
在 Linux 上,使用 gdb
进行调试时,需要确保在编译时生成带有调试信息的二进制文件。默认情况下,使用 gcc
或 g++
编译源代码时,使用动态链接,生成的二进制文件通常是以 release 模式 生成的,即没有包含调试信息(比如变量名、函数名等)。为了能够使用 gdb
调试代码,必须在编译时加上 -g
选项,这样可以在二进制文件中包含调试符号和信息。
举例:
gcc -g -o my_program my_program.c # 编译时加-g选项,生成带调试符号的二进制程序
gcc -o my_program my_program.c -g //-g放在末尾,效果也一样
这使得 gdb
能够读取并提供源代码级的调试信息。
程序发布方式:
1、debug版本:程序本身会被加入更多的调试信息,以便于进行调试。
2、release版本:不会添加任何调试信息,是不可调试的。
在Linux当中gcc/g++默认生成的可执行程序是release版本的,是不可被调试的。如果想生成debug版本,就需要在使用gcc/g++生成可执行程序时加上**-g选项**。
对同一份源代码分别生成其release版本和debug版本的可执行程序,并通过ll指令可以看到,debug版本发布的可执行程序的大小比release版本发布的可执行程序的大小要大一点,其原因就是以debug版本发布的可执行程序当中包含了更多的调试信息。
gdb命令汇总
【进入gdb】
指令: gdb 文件名
【调试】
1)「run/r」:运行代码(启动调试)。
2)「next/n」:逐过程调试。
3)「step/s」:逐语句调试。
4)「until 行号」:跳转至指定行。
5)「finish」:执行完当前正在调用的函数后停下来(不能是主函数)。
6)「continue/c」:运行到下一个断点处。
7)「set var 变量=x」:修改变量的值为x。
【显示】
1)「list/l n」:显示从第n行开始的源代码,每次显示10行,若n未给出则默认从上次的位置往下显示.。
2)「list/l 函数名」:显示该函数的源代码。
3)「print/p 变量」:打印变量的值。
4)「print/p &变量」:打印变量的地址。
5)「print/p 表达式」:打印表达式的值,通过表达式可以修改变量的值。
6)「display 变量」:将变量加入常显示(每次停下来都显示它的值)。
7)「display &变量」:将变量的地址加入常显示。
8)「undisplay 编号」:取消指定编号变量的常显示。
9)「bt」:查看各级函数调用及参数。
10)「info/i locals」:查看当前栈帧当中局部变量的值。
【断点】
1)「break/b n」:在第n行设置断点。
2)「break/b 函数名」:在某函数体内第一行设置断点。
3)「info breakpoint/b」:查看已打断点信息。
4)「delete/d 编号」:删除指定编号的断点。
5)「disable 编号」:禁用指定编号的断点。
6)「enable 编号」:启用指定编号的断点。
【退出gdb】
1)「quit/q」:退出gdb。
具体使用:
开始使用 gdb
启动 gdb
首先,打开终端并启动 gdb
,指向需要调试的二进制文件。
gdb ./binFile # 启动 gdb,加载待调试的二进制文件
退出 gdb
- 使用
ctrl + d
或quit
命令退出gdb
调试器。
quit # 退出 gdb 调试器
调试命令
以下是一些常见的调试命令和它们的使用方法。
- 调试命令
list
/ l
查看源代码内容:
list
:显示程序的源代码,默认从当前行开始显示,显示 10 行。list 行号
:显示指定行号附近的源代码。list 函数名
:显示指定函数的源代码。
举例:
list # 显示当前位置周围的 10 行代码
list 20 # 显示第 20 行周围的 10 行代码
list main # 显示 main 函数的源代码
r
或 run
运行程序:
run
或r
:启动程序的执行,直到遇到断点或程序结束。
举例:
run # 运行程序
n
或 next
(类似于VS里的逐过程,F10)
单步执行当前行,不进入函数:
next
或n
:执行当前行,并跳到下一行。如果当前行是一个函数调用,next
会执行整个函数,跳过函数内部。
举例:
next # 执行当前行,跳到下一行
s
或 step
(类似于VS里的逐语句,F11)
单步进入函数调用:
step
或s
:单步执行当前行,若当前行是一个函数调用,会进入该函数内部并停止。
举例:
step # 如果当前行是函数调用,进入该函数执行
break
或 b
设置断点:
break
在指定的行号设置断点。break
** 函数名**:在某个函数的开始处设置断点。
举例:
break 10 # 在第 10 行设置断点
break main # 在 main 函数开始处设置断点
info break
查看当前断点信息:
info break
:列出所有当前设置的断点和它们的状态。
举例:
info break # 查看当前所有断点
continue
或 c
继续执行程序:
continue
或c
:从当前停止的位置继续执行程序,直到遇到下一个断点。也就是从一个断点运行到下一个断点(范围查找)
举例:
continue # 继续程序执行,直到下一个断点
finish
执行完当前函数后停止:
finish
:继续执行当前函数,直到函数返回,并返回到调用该函数的地方。
举例:
finish # 执行到当前函数返回,然后停下来
print
或 p
打印变量的值:
print
或p
:打印一个变量的值,可以通过p
打印任何合法的表达式。
举例:
print x # 打印变量 x 的值
print a + b # 打印表达式 a + b 的值
p &b //打印变量b的地址
修改变量的值
set var
:修改变量的值。(主要用于测试)
举例:
set var x = 10 # 将变量 x 的值修改为 10
delete breakpoints
/ d
删除所有断点:
delete breakpoints
:删除当前所有设置的断点。
举例:
delete breakpoints # 删除所有断点
delete breakpoints n
删除指定编号的断点:
delete breakpoints n
:删除指定编号的断点。
举例:
delete breakpoints 1 # 删除编号为 1 的断点
disable breakpoints
/ disable 和 enable breakpoints
/enable
禁用或启用断点:
disable breakpoints
:禁用所有断点。enable breakpoints
:启用所有断点。
举例:
disable breakpoints # 禁用所有断点
enable breakpoints # 启用所有断点
info locals
查看当前栈帧的局部变量:
info locals
:显示当前函数栈帧中的所有局部变量及其值。
举例:
info locals # 查看当前函数栈帧中的局部变量
int a;
info a;
undisplay
取消显示某个变量的值:
undisplay
:取消显示某个变量或表达式的值。
举例:
undisplay # 取消显示所有变量
display
display
命令可以让你在程序调试时持续查看一个变量的值,尤其在调试过程中,当程序在多个地方停下时,它会自动显示指定的变量值。
display <expression>
**<expression>**
是你希望跟踪的变量、函数或表达式。例如,你可以显示一个变量的值,或者追踪某个表达式的计算结果。
until
跳到指定行号:
until
:跳到指定的行号,直到遇到该行或程序终止。
举例:
until 20 # 跳到第 20 行
backtrace
或 bt
查看函数调用栈:
backtrace
或bt
:打印函数调用栈,显示函数调用的层级和参数。
举例:
backtrace # 查看当前函数调用栈
quit
退出 gdb:
quit
:退出 gdb 调试器。
举例:
quit # 退出 gdb
**finish**
** 命令的作用**
当你在 gdb
中调试时,可能已经进入了一个函数的内部,并在该函数的某个位置停住了。如果你希望让程序继续执行当前函数的剩余部分,直到返回,并且不希望单步执行每一行代码,那么 finish
命令就很有用。
finish
- 执行过程:
finish
会执行当前栈帧中的代码,直到该函数返回。返回时,gdb
会停在函数调用的位置。通过finish
命令,调试器可以跳过当前函数的其余代码,而不需要逐行执行。 - 返回值:
finish
命令在执行完当前函数后,会显示当前函数的返回值(如果有返回值的话)。例如,如果函数有一个返回值,gdb
会输出该值,方便你查看函数的执行结果。
Linux项目自动化构建工具 - make/Makefile
make
是一个自动化构建工具,用于自动化编译项目中的源代码。它广泛用于大型项目的编译过程中,尤其是在 Unix-like 操作系统(如 Linux)中。通过使用 make
和 Makefile
,开发者可以大幅提高软件开发的效率。
Makefile
是一个文本文件,包含了一系列编译规则,指示make
如何自动化地编译程序。make
是命令工具,用于解析和执行Makefile
中的规则。
在大型工程中,源代码可能被分成多个文件,并且这些文件按类型、功能、模块等进行组织。Makefile
文件则定义了这些文件之间的依赖关系和编译顺序,从而实现了自动化编译。通过执行 make
命令,make
会根据 Makefile
中的规则,自动进行编译、链接等操作。
make/Makefile的重要性
- 会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力
- 一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作
- makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。
- make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。
- make是一条命令,makefile是一个文件,两个搭配使用,完成项目自动化构建。
依赖关系和依赖方法
在使用make/Makefile前我们首先应该理解各个文件之间的依赖关系以及它们之间的依赖方法。
依赖关系: 文件A的变更会影响到文件B,那么就称文件B依赖于文件A。
- 例如,test.o文件是由test.c文件通过预处理、编译以及汇编之后生成的文件,所以test.c文件的改变会影响test.o,所以说test.o文件依赖于test.c文件。
依赖方法: 如果文件B依赖于文件A,那么通过文件A得到文件B的方法,就是文件B依赖于文件A的依赖方法。
- 例如,test.o依赖于test.c,而test.c通过gcc -c test.c -o
test.o指令就可以得到test.o,那么test.o依赖于test.c的依赖方法就是gcc -c test.c -o test.o。
Makefile
** 的基本结构和规则**
- 基本语法
在 Makefile
中,每条规则通常由三部分组成:
target: dependencies
command
- target:目标文件或伪目标(例如,
hello
或clean
)。 - dependencies:目标文件的依赖文件。
- command:执行的命令,用于生成目标文件或执行操作。
变量定义
在 Makefile
中,可以定义变量来简化规则的书写。例如:
CC = gcc
//定义编译器,通常为 gcc
CFLAGS = -Wall -g
//定义编译器的选项,这里使用 -Wall 启用所有警告信息,-g 生成调试信息。
- 一个简单的
Makefile
示例
假设我们有一个 C 项目,其中有一个 hello.c
文件,我们想要编译它生成 hello
可执行文件:
hello: hello.o
gcc hello.o -o hello
hello.o: hello.c
gcc -c hello.c -o hello.o
hello: hello.o
:目标hello
依赖于hello.o
,如果hello.o
改变,hello
会被重新编译。hello.o: hello.c
:目标hello.o
依赖于hello.c
,如果hello.c
改变,hello.o
会被重新编译。
在这个例子中,make
会首先检查 hello.o
是否存在或是否需要更新。如果需要,make
会使用 gcc -c hello.c -o hello.o
编译 hello.c
文件生成 hello.o
文件。接着,make
会执行 gcc hello.o -o hello
将目标文件 hello.o
链接成最终的可执行文件 hello
。
- **伪目标 **
.PHONY
伪目标是指不对应实际文件的目标,通常用于清理操作(如 clean
)。伪目标确保每次执行 make clean
时,相关的命令都会被执行。
.PHONY: clean
clean:
rm -f hello.o hello
.PHONY: clean
是告诉 make
,clean
这个目标是伪目标(也可以理解为始终执行的命令),即使当前目录下有名为 clean
的文件,也会重新执行 mybin
相关的规则,而不是跳过它。
.PHONY
用于避免 make
将文件 clean
当作一个目标,避免因文件存在而跳过执行目标相关命令。
Makefile
** 示例代码解析**
hello: hello.o
gcc hello.o -o hello
hello.o: hello.s
gcc -c hello.s -o hello.o
hello.s: hello.i
gcc -S hello.i -o hello.s
hello.i: hello.c
gcc -E hello.c -o hello.i
.PHONY: clean
clean:
rm -f hello.i hello.s hello.o hello
- 目标:
hello
:hello
目标依赖于hello.o
,通过执行gcc hello.o -o hello
来生成hello
可执行文件。 - 目标:
hello.o
:hello.o
依赖于hello.s
,通过gcc -c hello.s -o hello.o
编译生成目标文件hello.o
。 - 目标:
hello.s
:hello.s
依赖于hello.i
,通过gcc -S hello.i -o hello.s
将预处理后的文件转换为汇编文件。 - 目标:
hello.i
:hello.i
依赖于hello.c
,通过gcc -E hello.c -o hello.i
对 C 文件进行预处理,生成hello.i
。
清理操作:
每个目标都由它依赖的文件触发编译。如果依赖文件比目标文件更新,make
会重新编译。
clean
规则是一个伪目标,它不会生成文件,而是用于删除临时文件和中间文件。make clean
会删除 hello.i
、hello.s
、hello.o
和 hello
文件,以便重新构建。
make
** 的工作原理**
make
会根据 Makefile
中定义的规则来处理源文件和目标文件之间的依赖关系。基本的工作流程如下:
- **查找 **
Makefile
:- 当运行
make
命令时,make
会在当前目录查找名为Makefile
或makefile
的文件。
- 当运行
- 查找目标文件:
make
会查看Makefile
中定义的第一个目标文件(例如上面的hello
),并将其作为最终的目标文件进行处理。
- 检查文件依赖:
make
会检查目标文件的依赖关系,如果目标文件不存在或者依赖文件比目标文件新(例如源文件比目标文件新),则会重新编译。
- 递归依赖检查:
- 如果目标文件依赖于其他文件(例如
hello.o
依赖于hello.c
),make
会逐步递归地处理每个依赖,直到所有的目标文件都满足编译条件。
- 如果目标文件依赖于其他文件(例如
- 执行编译命令:
make
会根据Makefile
中的规则,执行相应的命令(例如gcc
编译源文件),直到生成最终的执行文件。
- 错误处理:
- 如果在查找依赖或执行命令时遇到错误,
make
会停止,并报错。
- 如果在查找依赖或执行命令时遇到错误,
项目清理
在我们每次重新生成可执行程序前,都应该将上一次生成可执行程序时生成的一系列文件进行清理,但是如果我们每次都手动执行一系列指令进行清理工作的话,未免有些麻烦,因为每次清理时执行的都是相同的清理指令,这时我们可以将项目清理的指令也加入到Makefile文件当中。
像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,但我们可以显示要make执行。
注: 一般将这种clean的目标文件设置为伪目标,用.PHONY修饰,伪目标的特性是:总是被#执行。
引入:多文件编译
为什么 make
和 Makefile
重要
一个项目通常有很多源文件,而且不同的源文件可能相互依赖。通过使用 Makefile
,你可以指定:
- 哪些文件需要先编译。
- 哪些文件修改后需要重新编译。
- 以及其他一些更复杂的操作,如清理中间文件、生成执行文件等。
这大大减少了手动管理编译过程的工作,提高了开发效率和准确性。
比如下面的例子:
当你的工程当中有多个源文件的时候,应该如何进行编译生成可执行程序呢?
首先,我们可以直接使用gcc指令对多个源文件进行编译,进而生成可执行程序。
但进行多文件编译的时候一般不使用源文件直接生成可执行程序,而是先用每个源文件各自生成自己的二进制文件,然后再将这些二进制文件通过链接生成可执行程序。
原因:
- 若是直接使用源文件生成可执行程序,那么其中一个源文件进行了修改,再生成可执行程序的时候就需要将所以的源文件重新进行编译链接。
- 而若是先用每个源文件各自生成自己的二进制文件,那么其中一个源文件进行了修改,就只需重新编译生成该源文件的二进制文件,然后再将这些二进制文件通过链接生成可执行程序即可。
注意: 编译链接的时候不需要加上头文件,因为编译器通过源文件的内容可以知道所需的头文件名字,而通过头文件的包含方式(“尖括号”包含和“双引号”包含),编译器可以知道应该从何处去寻找所需头文件。
但是随着源文件个数的增加,我们每次重新生成可执行程序时,所需输入的gcc指令的长度与个数也会随之增加。这时我们就需要使用make和Makefile了,这将大大减少我们的工作量。
步骤一: 在源文件所在目录下创建一个名为Makefile/makefile的文件。
步骤二: 编写Makefile文件。
Makefile文件最简单的编写格式是,先写出文件的依赖关系,然后写出这些文件之间的依赖方法,依次写下去。
编写完毕Makefile文件后保存退出,然后在命令行当中执行make指令便可以生成可执行程序,以及该过程产生的中间产物。
Makefile文件的简写方式:
- $@:表示当前规则中的 目标文件(target file)(冒号左侧)。
- $^:表示当前规则中的 所有依赖文件(prerequisites),以空格分隔(冒号右侧全部)。
- $<:表示依赖关系中的第一个依赖文件(冒号右侧第一个)。
例如以上Makefile文件可以简写为:
说明: gcc/g++携带-c选项时,若不指定输出文件的文件名,则默认输出文件名为xxx.o,所以这里也可以不用指定输出文件名。
判断是否需要重新编译目标文件
在 make
和 Makefile
中,make
工具通过文件的 时间戳 来判断是否需要重新编译目标文件。它会根据目标文件(例如 mybin
)和其依赖文件(例如 mytest.c
)的修改时间来决定是否执行编译操作。
工作原理
当你执行 make
命令时,make
会检查 Makefile
中列出的目标和依赖项。具体来说,它会检查以下几点:
- 检查目标文件和依赖文件的时间戳:
make
会查看目标文件(如mybin
)和依赖文件(如mytest.c
)的修改时间。如果目标文件的时间戳比依赖文件的时间戳更新(即目标文件是最新的),make
会认为目标文件是“最新的”,并跳过编译过程。- 如果依赖文件的时间戳比目标文件的新(即依赖文件已修改),
make
会认为目标文件过时,并重新执行编译命令。
make
** 判断文件是否需要重新编译**:- 如果目标文件(如
mybin
)不存在,或者目标文件的时间戳比依赖文件的时间戳要旧,make
会执行与目标文件相关联的命令(例如gcc
命令)来重新编译并生成目标文件。 - 如果目标文件与依赖文件的时间戳一致,且目标文件是最新的,
make
会输出"make: 'mybin' is up to date."
,并跳过编译。
- 如果目标文件(如