【Linux系统编程】编译器gcc/g++
【Linux系统编程】编译器gcc/g++
- 1. 如何让普通用户sudo提权
- 2. gcc/g++的简单介绍和使用
- 3. gcc/g++编译的四个阶段
- 3.1 预处理(Preprocessing)
- 3.2 编译(Compilation)
- 3.3 汇编(Assembly)
- 3.4 链接(Linking)
- 3.4.1 动态链接与静态链接
- 4. 动态库与静态库
- 5. gcc/g++其他常用选项
1. 如何让普通用户sudo提权
正常情况下,普通用户是不能sudo提权的。

这是因为lisi没有被添加到系统的白名单中,系统默认不信任你,那么我们要怎么做才能把lisi加入到白名单中呢?
1.使用root用户输入指令:vim /etc/sudoers,建议了解vim的基本操作再考虑将普通用户加入到白名单。
2.打开后用set nu让它显式行,100 gg,就跳到白名单位置了,将root那一行yy复制一下,p粘贴到下一行,把名字改成lisi即可,退出vim时进入底行模式输入wq!,!表示强制,因为该文件即使是root系统也不想让其修改,但root是特权阶级,可以强制修改。



此时lisi就可以sudo提权了,只不过还要输入lisi的密码。
2. gcc/g++的简单介绍和使用
gcc是用来编译C语言的,g++是用来编译C++的。
下面我们写一个C程序,通过gcc编译一下,让大家了解一下它的简单使用。

编译后,默认生成一个叫a.out的可执行程序。

也可以通过指令:gcc code.c -o code.exe,指定生成的可执行程序叫code.exe。

g++的使用与gcc完全相同,只是把gcc换成g++即可。
3. gcc/g++编译的四个阶段
gcc/g++的编译过程通常分为四个清晰的阶段,你可以通过指定编译选项来控制编译器在每个阶段结束后停止,这非常有助于理解编译原理和调试。
下面我们用该代码去解释编译的四个阶段
#include <stdio.h>#define M 100
//#define VERSION1 1int main()
{#ifdef VERSION1printf("我是version1版本的功能\n");
#elseprintf("我是version2版本的功能\n");
#endifprintf("hello world, 1, %d\n", M);//printf("hello world, 2, %d\n", M);//printf("hello world, 3, %d\n", M);//printf("hello world, 4, %d\n", M);//printf("hello world, 5, %d\n", M);//printf("hello world, 6, %d\n", M);//printf("hello world, 7, %d\n", M);//printf("hello world, 8, %d\n", M);printf("hello world, 9, %d\n", M);return 0;
}
3.1 预处理(Preprocessing)
- 作用:处理源代码中以#开头的预处理指令。
- 主要工作:展开头文件(#include)、替换宏(#define)、执行条件编译(#ifdef、#ifndef)、删除注释。
- 命令示例:gcc -E code.c -o code.i
- 输出文件:生成以.i为后缀的预处理后的文件。

条件编译的应用
假设一款软件VIP用户比普通用户有更多的功能可以使用,那么如果维护两份代码就会很消耗成本,比如普通用户的版本有个bug要维护一下,那么VIP用户在相同的功能处也要去维护一下,太麻烦了,条件编译就让其变得简单了,普通用户调用普通用户的版本,利用条件编译把VIP用户才能享用的功能裁剪掉,VIP用户调用VIP用户的版本。
3.2 编译(Compilation)
- 作用:将预处理后的C/C++代码翻译成汇编语言。
- 主要工作:进行严格的语法和语义检查。如果有代码错误,编译器会在此阶段报错并停止。检查无误后,生成与目标处理器架构相关的汇编代码。
- 命令示例:gcc -S code.i -o code.s
- 输出文件:生成以 .s为后缀的汇编语言文件。

3.3 汇编(Assembly)
- 作用:将汇编代码翻译成机器可以直接识别的二进制目标代码。
- 主要工作:调用汇编器将 .s文件转换为目标文件。此文件已经是二进制格式,但还不可执行。
- 命令示例:gcc -c code.s -o code.o
- 输出文件:生成以 .o为后缀的目标文件,也叫可重定位目标二进制文件。

为什么.o文件已经是二进制文件了,但却无法执行呢?
因为我们写的代码用到了库的方法,而头文件展开只有方法的声明,没有定义,需要在链接阶段找到库的方法的定义才能运行。

ldd命令,可以查看该二进制程序依赖什么库。
3.4 链接(Linking)
- 作用:将多个目标文件(.o)和所需的库文件组合成一个完整的可执行文件。
- 主要工作:解析程序中调用的外部函数(如 printf)的地址。链接器会找到这些函数在标准库(如 libc.so.6)或其他指定库中的实现,并将它们“链接”到一起。
- 命令示例:gcc code.o -o code.exe
- 输出文件:生成最终的可执行文件。


3.4.1 动态链接与静态链接
在我们的实际开发中,不可能将所有代码放在⼀个源⽂件中,所以会出现多个源⽂件,⽽且多个源⽂件之间不是独⽴的,⽽会存在多种依赖关系,如⼀个源⽂件可能要调⽤另⼀个源⽂件中定义的函数,但是每个源⽂件都是独⽴编译的,即每个.c⽂件会形成⼀个.o⽂件,为了满⾜前⾯说的依赖关系,则需要将这些源⽂件产⽣的⽬标⽂件进⾏链接,从⽽形成⼀个可以执⾏的程序。这个链接的过程就是静态链接。
静态链接的优缺点
优点:程序一旦链接完成形成了可执行程序,就不依赖库,可执行程序可以独立运行。
缺点:体积大,比较消耗资源(内存空间,磁盘空间,网络空间)
动态链接的出现解决了静态链接中提到问题。
动态链接的基本思想是把程序按照模块拆分成各个相对独⽴部分,在程序运⾏时才将它们链接在⼀起形成⼀个完整的程序,⽽不是像静态链接⼀样把所有程序模块都链接成⼀个单独的可执⾏⽂件。
动态链接的优缺点
优点:动态库由于是共享库,所以可以有效节约空间(内存空间,磁盘空间,网络空间)
缺点:动态库一旦缺失会造成很多程序无法运行
4. 动态库与静态库
静态库是指编译链接时,把库⽂件的代码全部加⼊到可执⾏⽂件中,因此⽣成的⽂件⽐较⼤,但在运⾏时也就不再需要库⽂件了。
动态库与之相反,在编译链接时并没有把库⽂件的代码加⼊到可执⾏⽂件中,⽽是在程序执⾏时由运⾏时链接⽂件加载库,这样可以节省系统的开销。
gcc在编译时默认使⽤动态库。
gcc默认⽣成的⼆进制程序,是动态链接的,这点可以通过file命令验证。


Centos静态库安装
sudo yum install glibc-static libstd++-static -y
可以看到用静态库链接的可执行程序比动态库的大了100多倍。


5. gcc/g++其他常用选项
- -E 只激活预处理,这个不⽣成⽂件,你需要把它重定向到⼀个输出⽂件⾥⾯
- -S 编译到汇编语⾔不进⾏汇编和链接
- -c 编译到⽬标代码
- -o ⽂件输出到⽂件
- -static 此选项对⽣成的⽂件采⽤静态链接
- -g ⽣成调试信息。GNU调试器可利⽤该信息。
- -shared 此选项将尽量使⽤动态库,所以⽣成⽂件⽐较⼩,但是需要系统由动态库.
- -O0/-O1/O2/O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最⾼
- -w 不⽣成任何警告信息。
- -Wall⽣成所有警告信息。
