
 🔥博客主页: 小羊失眠啦.
 🎥系列专栏:《C语言》 《数据结构》 《C++》 《Linux》 《Cpolar》
 ❤️感谢大家点赞👍收藏⭐评论✍️

接上文,我们已经学习了 Linux 中的编辑器 vim 的相关使用方法,现在已经能直接在 Linux 中编写C/C++代码,有了代码之后就要尝试去编译并运行它,此时就可以学习一下 Linux 中的编译器 gcc/g++ 了,我们一般使用 gcc 编译C语言,g++ 编译C++(当然 g++ 也可编译C语言),这两个编译器我们可以当作一个来学习,因为它们的命令选项都是通用的,只是编译对象不同。除了编译器相关介绍外,本文还会库、自动化构建工具、提权等知识,一起来看看吧
文章目录
- 程序实现的两大环境
- 一、gcc/g++使用
- 1.1 gcc如何完成
- 1.1.1 预处理
- 1.1.2 编译
- 1.1.3 汇编
- 1.1.4 链接
 
 
- 二、动态库与静态库
- 2.1 动态库
- 2.2 静态库
 
- 三、自动化构建工具
- 3.1 Makefile文件
- 3.2 make指令
- 3.3 任务刷新策略
- 3.4 .PHONY伪目标
- 3.5 补充
 
- 四、sudo提权
程序实现的两大环境
任何一个C程序的实现都要经过翻译环境与执行环境。
在翻译环境中又分为4个部分,预编译、编译、汇编与链接。在各个阶段主要完成的任务有:
-  预编译(预处理):头文件的包含、注释的删除、#define符号的替换; 
-  编译:将C语言代码转化为汇编代码; 
-  汇编:把汇编指令转化为二进制指令; 
-  链接:合并符号表和段表,生成可执行程序。 

一、gcc/g++使用
1.1 gcc如何完成
通过gcc指令的不同选项可查看各阶段所形成的文件;
格式:gcc [选项] [目标文件名] -o [生成文件名]。
首先在test.c文件中写好C代码:
#include<stdio.h>
#define N 100
//#define ...
int main()
{
	int n = 0;
	printf("Hello World\n");
	printf("%d\n", n + N);
	return 0;
}
1.1.1 预处理
查看 test.c 预处理后的结果,-E选项的作用是让 gcc 在预处理结束后停止编译过程;-o 的作用是将预处理后的内容保存到test.i 文件中。
gcc -E test.c -o test.i

如图所示,预处理阶段进行了头文件包含、注释的删除、#define的替换。
1.1.2 编译
接下来将刚刚生成的 test.i 进行编译,并在编译之后停下来,将结果写入 test.s 中。gcc所用选项为 -S。
gcc -S test.i -o test.s

我们虽然可能没学习过汇编语言,但依旧隐约认识到这些就是汇编代码;可见编译阶段就是将C代码翻译为汇编指令。
1.1.3 汇编
gcc所用选项为 -c;-o 将结果写入到 test.o 中。
gcc -c test.s -o test.o

正如我们所见,汇编完成之后都这这样的乱码。其实汇编之后,生成的文件为二进制文件,是用来给计算机看的,咱们已经看不懂了。
1.1.4 链接
编译完成之后就进入了链接阶段,链接完成之后就会生成可执程序 mytest了。
gcc test.o -o mytest

但是关于链接,我们需要知道它在链接什么。
我们是否好奇过为什么我们明明没有定义过函数 printf 、return …等等的函数,但却可以使用它的功能?
其实,系统把这些函数的实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函 数 printf 了,而这也就是链接的作用。
二、动态库与静态库
函数库一般分为动、静态库两种。
2.1 动态库
动态库 即通过 动态链接 的库,动态库 又称 共享库,因为 动态库 中的内容是被所有程序共享的,简言之 动态库 中的代码只需要存在一份,程序需要使用时,直接通过对应位置调用就行了
gcc 进行函数库的链接时可选择静态链接或者动态链接。
Linux 中默认使用 动态链接 的方式,我们可以通过指令 ldd 最终生成的文件 来查看最终生成文件的链接情况

libXXX.so 是动态链接的标志
- 其中 lib是前缀
- .so是后缀
- 去掉前缀与后缀,就是最终调用的库
举例:libc.so 去掉前缀与后缀,最终为 c ,可以看出文件最终调用的是C语言共享库,即 动态链接
动态链接 主要依赖不同函数在库中的位置信息进行调用,只有一份代码库,比较节省空间
我们还可以通过 file 命令查看文件详细信息

这也验证了 Linux 默认使用 动态链接 的现象
2.2 静态库
除了 动态库 外,还有静态库 ,采用静态链接的方式;静态链接不同与动态链接共享的方式,如果程序调用静态库,会将自己所需要的代码拷贝至程序中,完成拷贝后,后续不需要再调用 静态库
如果想采用 静态链接 链接的方式编译程序,需要在编译时加上 -static 选项,当然前提是得有 静态库,没有的可以通过 yum install -y glibc-static 下载 静态库
当然我们也可以通过 ldd 最终生成的文件 查看是否为 静态链接
#下载静态库
yum install -y glibc-static
#采取静态链接的方式编译程序
gcc -static test.c -o Test_static
#查看文件的链接方式
ldd Test_static

静态库 命名为 libXXX.a
- lib是前缀
- .a是后缀
- 去掉前缀与后缀,就是最终调用的库
我们也可以采用 file 命令查看详细信息
file Test_static

静态链接 因为是直接将需要的代码拷贝到程序中,因此最终生成的文件会变大,比较占空间
对比二者生成的文件大小
#采用静态链接
gcc -static test.c -o Test_static
#默认采用动态链接
gcc  test.c -o Test
若在静态链接时出错,可能是因为你的 Linux 没有安装C语言的静态库,须手动安装。

两种连接方式生成的文件大小几乎相差百倍。
优劣对比
动态库 和 静态库 各有优缺点,不然也不会同时存在两种库了
| 区别 | 动态库 | 静态库 | 
|---|---|---|
| 调用方式 | 通过函数位置进行调用 | 直接将需要的函数拷贝至程序中 | 
| 依赖性(运行时) | 需要依赖于动态库 | 可以独立于静态库运行 | 
| 空间占用 | 共享动态库中的代码,空间占用少 | 拷贝代码会占用大量空间 | 
| 加载速度 | 调用函数,加载速度慢 | 直接运行,加载速度快 | 
小结
动态库
- 优点 
  - 可以实现不同进程间的资源共享
- 对于函数的升级只需要替换动态库文件,不需要重新编译程序
- 可以控制是否加载动态库,不调用函数时就不加载
 
- 缺点 
  - 需要调用函数,加载速度较慢
- 程序运行需要依赖动态库
 
静态库
- 优点 
  - 所需函数直接拷贝至程序中,运行速度快
- 程序运行无需依赖库,便于移植
 
- 缺点 
  - 对于函数的升级,需要重新进行编译
- 同一份代码可能出现重复拷贝的情况,浪费空间
 
三、自动化构建工具
自动化构建工具可以帮助我们完成设置好的指令,指令为 make ,我们可以通过提前设置,实现源文件的快速编译
3.1 Makefile文件
要想使用 make 指令,就得先有 Makefile 文件,Makefile 文件中主要编写任务,而任务由 依赖关系 + 依赖方法 构成
1.依赖关系
- 比如源文件为 test.c,编译后生成的文件为mytest,那么两者间的依赖关系为mytest:test.c这组依赖关系我们可以写入Makefile文件中
2.依赖方法
- 有了关系后,就要描述具体实现方法,比如上面那组 依赖关系的依赖方法为gcc test.c -o mytest将依赖方法也写入Makefile文件中

完成上面两个内容的编写后,我们就得到了一个基本的自动化任务,输入 make mytest 即可编译 test.c 文件,生成 mytest
#执行自动化指令,编译test.c文件
make mytest

注意: 同一个自动化任务,执行成功后,如果相关文件最近没有发生改变,那么无法再次执行自动化任务
3.2 make指令
上面展示了如何编写 Makefile 文件并执行相关任务,使用了 make file 指令,并没有直接使用 make指令,因为这个指令还是有些说法的
单纯输入 make 指令时,默认执行 Makefile 中的第一个任务,当任务成功执行后,不再继续执行后续任务(一个 Makefile 文件中,可以有多个任务),由此可见,单纯的 make 指令只会执行第一个自动化任务


当我们编写好 Makefile 文件后,可以通过 make 任务名 调用任务,任务名就是 依赖关系 中的左侧名;也可以直接通过 make 调用第一个任务
3.3 任务刷新策略
前面说过,同一个方法如果成功执行过,在原文件最近修改时间没有发生变化时,无法再执行任务,这背后的原因是方法是否执行会先判断生成的目标文件是否为最新,如果为最新,就不再执任务
举例:重复执行 make myfile 任务
make myfile    //第一次执行任务,成功
make myfile    //第二次执行任务,失败,因为源文件最近没有被修改

想要再次执行任务也很简单,对源文件做出修改,或者直接 touch 一下源文件就行了,两种行为都会修改文件的最近修改时间,使源目标文件不是最新时间
3.4 .PHONY伪目标
.PHONY 是 Makefile 文件中的一个关键字,意为对某某对象生成伪目标,这样就能在不对源文件进行修改的情况下,重复执行任务了
//Makefile文件中
.PHONY:mytest


在使用关键字 .PHONY 对目标进行修饰后,可以无视任务刷新策略,重复执行任务了
不过这有什么意义呢?
 答:对于这种源文件来说,没有任何意义
.PHONY 这个关键字,一般是用来修饰 clean 任务,即清理解决方案,Makefile 实现为
//Makefile 文件中
.PHONY:clean
clean:
	rm -r mytest


换个角度想想,当我们把生成的原目标文件清理后,再执行任务,生成目标文件是一件很合理的事,也完全符合任务刷新策略
由此来看,.PHONY 也是很有用的
注意: 像 clean:这种半缺失 依赖方法 是合理的,毕竟清理这个任务也不需要任何对象,只需要单纯的执行删除(清理)指令就行了
3.5 补充
make 指令的工作原理是去 Makefile 文件中寻找任务执行,它的设计者为了确保普适性,创建 makefile 文件也是合法可用的
也就是说,我们创建 make 指令的任务源文件时,可以创建为 Makefile ,也可以创建为 makeile
四、sudo提权
权限,是一个让人又爱又恨的东西,它的安全性固然很重要,但有时候又太麻烦了,当我们普通用户想执行操作时,需要请 root 出马,比如最基本的下载软件指令,感觉有些小题大做了
为了解决这种不合理的现象,Linux 中就有 sudo 提权 这个概念,简单来说,就是暂时借助 root 的身份去完成某条指令

不过普通用户默认是没有赋予提权权限的,还是需要请 root 帮忙配置
 步骤如下
- 切换为 root用户
- 打开 /etc/sudoers这个文件
- 找到如下图所示区域,将需要提权的普通用户添加进去就行了

//root 身份下
vim /etc/sudoers    //打开这个配置文件,找到上图区域进行修改就行了
当 提权 配置完成后,普通用户遇到权限拒绝的场景时,只需要 sudo 指令 ,然后输入当前普通用户的密码,就可以暂时借助 root 的身份无视权限完成指令了
注意: sudo 后,输入的是当前普通用户的密码,不需要输入 root 密码,这样就能做到保护 root 的情况下,执行指令了



















