这篇文章我们继续学习Linux中的开发工具,今天要学的是:
Linux下的编译器——gcc/g++
1. 概念
我们先来简单了解一下它们的概念:
gcc (GNU Compiler Collection) 和 g++ 是 Linux 系统上最常用的编译器。它们是 GNU 组织开发的一套开源编译器工具集。
gcc:
gcc 是 GNU 编译器集合中的 C 语言编译器。
它支持多种 C 语言标准(如 ANSI C、ISO C89、ISO C99)以及一些扩展特性。
gcc 可以将 C 语言源代码编译成可执行文件,或者生成汇编代码和目标文件。
g++:
g++ 是 GNU 编译器集合中的 C++ 语言编译器。
它在 gcc 的基础上添加了对 C++ 语言的支持,包括标准 C++ 和一些扩展特性。
g++ 可以将 C++ 源代码编译成可执行文件,或者生成汇编代码和目标文件。
gcc 和 g++ 的选项基本上都是一样的,我们这里就重点以gcc为例来进行讲解。
2. gcc 的使用
通过上面的了解我们知道gcc 和 g++ 其实就是Linux系统上的编译器。
编译器其实就是是一种将高级程序代码(如 C、C++、Java 等)转换为低级机器代码或可执行文件的软件工具。
那在之前C语言的学习阶段,我们其实有一篇文章比较详细的讲解了一下一个C程序从源文件变成可执行程序的过程
大家如果不太清楚或者忘了的话可以复习一下——【C进阶】——我们写的代码是如何一步步变成可执行程序(.EXE)的?
编译器做的工作其实就是对应图中的翻译环境中的几步。
那至于详细的过程,我们这里就不再展开讨论了(大家可以看之前的文章),不过我们再来结合操作简单的复习一下
之前的时候我们讲解的并没有让大家关心具体的操作,那现在,大家就需要来学习一下操作过程中的这样命令了。
我先来创建一个源文件myfile.c
对于这样一个源文件,如果我们直接gcc编译的话,他会直接做完整个翻译过程,自动生成一个名为a.out
的可执行文件
我们就可以直接执行输出结果。
那其实这个可执行文件的名字我们是可以自己指定的
gcc -o 新生成文件名 原文件名
"-o"选项用于指定生成的可执行文件或目标文件的名称。
-o之后一定是加我们自己给新生成文件起的名字
对于程序的翻译,分为下面几个过程:
2.1 预处理(预编译)
预处理阶段主要完成头文件的展开、宏替换、条件编译和去掉注释等工作。
那上面我们演示的是从源文件直接生成可执行文件,那如果我想让它执行完预处理过程就停下来呢?
这里用到另一个选项:
-E
-E
:预处理之后就停下来
所以我执行gcc -E myfile.c
但是这样直接执行之后它会把预处理之后文件的内容直接显示到显示器上,这样不太好看。
所以我们也可以-o指定一下文件名,把它放到对应文件里
那对于预处理之后的文件,一般后缀为
.i
首先我们可以观察到预处理之后文件大小大了很多。
然后我们可以用vim打开观察一下,当然打开myfile.c的同时我们可以借助vs 文件名
同时显示myfile.i,分屏显示,对比观察一下
这里看到myfile.i的内容是比较多的,我们看看他又多少行
一共有800多行,当然这个其实我们C语言那篇文章也带大家看过,其实前面多的这么多东西就是头文件展开的内容,当然还会做一些其它动作比如注释的删除、宏替换和条件编译语句的处理等。
不过这里我们没用这么多东西。
2.2 编译
编译过程,就是把预处理之后的C语言代码转换成汇编代码
那如果我想让编译结束停下来呢?
-S
:编译结束就停止
那这里我们看到不指定名字的话,他自动把生成的文件命名为.s
后缀的(编译之后文件后缀为.s)
当然我们还可以自己指定
那我们打开看一下
那这里里面放的其实就是对应的汇编代码
2.3 汇编
汇编其实就是把汇编指令转化为二进制的机器指令,生成对应的可重定位的二进制目标文件。
那我想查看汇编之后的文件:
-c
:汇编结束就停止
当然这个我们是看不懂的,它是一种二进制文件
2.4 链接
链接过程是将多个目标文件(可重定位目标文件)以及库文件组合在一起,生成最终的可执行文件。
那要链接生成可执行文件的话其实就不用选项了:
直接对汇编生成的.o文件进行gcc就可以生成最终的可执行程序
就可以执行了
把这整个过程我们又过了一遍。
但是,下面关于链接过程中的某些内容,我们还要来探讨一下
3. 动态库和静态库
上面说到在链接过程中我们的程序会和用到的一些库链接到一起。
大家思考一个问题,为什么我们在Linux上可以进行C/C++代码的编译链接这些动作呢?
其实其中一个比较重要的原因就是Linux提供了这些语言所需要的开发库,如标准C库(libc)、标准C++库(libstdc++)以及其他各种系统库和第三方库。这些库提供了大量的函数和工具,方便开发者编写各种类型的应用程序。
那其实我们可以看一下我们当前的Linux系统上都提供了那些库:
ls /usr/include
在这个路径下
我们看到里面有些头文件其实是我们比较熟悉的。
那下面我们就来学习这样一条命令:
ldd
:ldd命令用于打印一个可执行文件或共享库文件依赖的动态链接库(shared library)列表。它会递归地检查可执行文件或共享库文件所依赖的其他库文件,以及这些依赖的库文件的依赖,一直到所有依赖的库文件列表打印完毕。
比如,对于我们上面生成的可执行程序,我们就可以使用ldd命令查看一下它都依赖了哪些库
我们看到这里打印出来有3条,不过我们重点关心一下第二个。
第二个libc.so.6
其实就是Linux中的C标准库。
另外我们在安装一些ide的时候,比如就拿我用的这个vs2022来说,我们安装它的时候,其实一个比较重要的工作就是安装相关的库之类的东西。
就类似这个图。
在这里涉及到一个重要的概念——库(函数库)
我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?
最后的答案是:
系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能使用函数“printf”了,而这也就是链接的作用
那说到库,其实分为两种(库本质也是文件):
- 动态库
在Linux中,动态库一般是以
lib
开头,.so
为后缀
即libXXXXX.so
- 静态库
静态库一般以
lib
开头,.a
为后缀
即libXXXXX.a
3.1 动静态库的理解
那这里提到的动态库和静态库该怎么理解呢?
当我们谈论库(Library)时,可以将其比作图书馆,而静态库和动态库则是两种不同的图书存放方式。
想象一下,你是一名学生,图书馆中有很多有用的书籍,供你在学习过程中参考使用。
静态库(Static Library) 类似于你个人的书包,你从图书馆中选择了一些书籍,把它们拷贝到你的书包里。这些书籍是你个人拥有的,可以在需要的时候直接使用。当你需要使用这些书籍时,你只需从书包中取出,不需要依赖图书馆,也不会影响其他学生。
在编程中,静态库是在编译时将库的代码和程序代码链接在一起,形成一个单独的可执行文件。这意味着静态库的代码被复制到了最终的可执行文件中(这种链接方式我们称为静态链接),程序在运行时不需要外部的库文件依赖。这样做的好处是,程序更加独立,可以在不同的系统中运行,不受外部环境的影响。
但是生成的文件比较大。
动态库(Dynamic Library) 类似于图书馆中的共享书架,每个学生都可以访问这些书架上的书籍。当你需要使用这些书籍时,你可以从书架上取出,使用完毕后放回书架上供其他人使用。这意味着多个程序可以共享同一个动态库,减少了存储空间的占用。
在编程中,动态库是在运行时由操作系统加载的库文件,程序在运行时需要由链接器引入动态库,才能使用其中的函数或资源。可执行文件中只包含对库函数的引用或者说地址,而不复制库的代码和数据(动态链接)。这样做的好处是,多个程序可以共享同一个动态库,减少了内存的占用和可执行文件的大小。
总结起来:
静态库将库的代码复制到可执行文件中,使得程序独立运行;动态库则共享在操作系统中,减少了内存占用和可执行文件的大小。静态库适合小型独立程序,而动态库适合大型程序或多个程序共享使用。
3.2 默认是动态链接,我们如何进行静态
然后想告诉大家,在我们的Linux上,默认只有动态库,进行的是动态链接
之前我们学过一个file指令,它可以更清晰的显示一个文件的类型
我们执行file myfile.exe
就可以看到里面有一个dynamically linked
,它的意思就是动态链接
那如果我们想进行静态链接,能做到吗?
可以的。
不过呢,一般我们的Linux上默认只有动态库,所以,如果想进行静态链接的话,需要先安装一下静态库:
yum install -y glibc-static libstdc++-static
把指令给大家,大家直接执行就行(普通用户+sudo),这条指令是把C和C++的静态库都安装上的。
然后就可以进行静态链接了
对应的命令是
gcc -static
:静态链接
🆗,大家看到了吗,静态链接生成的可执行文件比动态链接生成的大了好多好多。
我们也可以用file查看一下静态链接生成的可执行文件
里面也能看到静态链接statically linked
关于动静态库我们先了解到这里,后续还会进行更深入的学习…