🔥博客主页: 小羊失眠啦.
🎥系列专栏:《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
的情况下,执行指令了