0


【Linux】编译器 - gcc && 函数库


一、背景知识

使用 vim 编辑器完成代码书写之后,我们需要使用 Linux 中的编译器 gcc 来对程序进行翻译,其本质是把程序从文本类文件翻译成二进制可执行文件,翻译过程包括:预处理(进行宏替换)、编译(生成汇编)、汇编(生成机器可识别代码)、连接(生成可执行文件或库文件)。

二、gcc如何执行

1、预处理

主要完成头文件展开、条件编译、宏替换、去注释等等工作。

预处理结构后,C语言还是C语言,没有发生变化。

预处理指令:

gcc -E [目标文件名] -o [生成文件名]

-E :从现在开始进行程序的翻译,预处理做完,就停下来。

-o :指定生成文件的文件名。-o 选项的位置可以不固定,但是 -o 选项后面紧跟着的,一定是生成的文件的名称。

我们先创建一个文件 myfile.c ,使用 vim 编译器写入代码:

保存退出后,对其进行预处理操作:

[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ vim myfile.c
[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ gcc -E myfile.c -o myfile.i
[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ ll
total 24
-rw-rw-r-- 1 ljb ljb   525 Jan 12 17:56 myfile.c
-rw-rw-r-- 1 ljb ljb 17086 Jan 12 17:57 myfile.i

可以看到已经生成了一个可执行文件 myfile.i 。我们把它打开,并与 myfile.c 对比:

可以看到原本 33 行的代码,预处理之后变成了 800 多行。这是因为 预处理 工作把程序里的头文件都拷贝到了源文件之中,这个工作叫做头文件展开。

这里打印了 "hello PRINT" ,而没有打印 "hello None",是因为在程序开始时定义了宏 PRINT ,所以 "hello None" 被条件编译裁剪掉了。

2、编译

完成将c语言编译,形成汇编语言。

编译指令:

gcc -S [目标文件名] -o [生成文件名]

-S :从现在开始进行程序的翻译,编译做完,就停下来。

我们对刚刚预处理形成的 myfile.i 文件进行编译:

[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ gcc -S myfile.i -o myfile.s
[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ ll
total 28
-rw-rw-r-- 1 ljb ljb   525 Jan 12 20:16 myfile.c
-rw-rw-r-- 1 ljb ljb 17086 Jan 12 19:56 myfile.i
-rw-rw-r-- 1 ljb ljb   681 Jan 12 20:17 myfile.s

生成了文件 myfile.s ,我们对比一下 myfile.s 与 myfile.i:

此时,C语言已经变为了汇编语言,有效代码变为 42 行。

3、汇编

将汇编语言文件转换成可重定位目标二进制文件,这些二进制文件不可以被执行,文件的后缀为 **.**o 。对应到 Windows 系统中,相同类型文件的后缀为 .obj ,有几个源文件,就有几个 obj 文件。

汇编指令:

gcc -c [目标文件名] -o [生成文件名]

-c : 从现在开始进行程序的翻译,汇编做完,就停下来。

我们对刚刚预处理形成的 myfile.s 文件进行编译:

[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ gcc -c myfile.s -o myfile.o
[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ ll
total 32
-rw-rw-r-- 1 ljb ljb   525 Jan 12 20:16 myfile.c
-rw-rw-r-- 1 ljb ljb 17086 Jan 12 19:56 myfile.i
-rw-rw-r-- 1 ljb ljb  1920 Jan 12 20:27 myfile.o
-rw-rw-r-- 1 ljb ljb   681 Jan 12 20:26 myfile.s

生成了文件 myfile.o ,我们打开该文件:

可以看到都是看不懂的乱码,这就说明该文件已经不是文本文件,而是一个二进制文件了。这个二进制文件是不可执行的。

4、链接

将我们自己形成的** .**obj文件和库文件进行某种合并,形成可执行程序。在我们进行链接的时候,把函数库对应的程序函数的地址拷贝到可执行程序里。

链接 / 编译指令:

gcc [目标文件名] -o [生成文件名]

我们对刚刚生成的 myfile.o 文件进行链接:

[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ gcc myfile.o -o myfile1
[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ ll
total 44
-rwxrwxr-x 1 ljb ljb  8440 Jan 12 20:40 myfile1
-rw-rw-r-- 1 ljb ljb   525 Jan 12 20:16 myfile.c
-rw-rw-r-- 1 ljb ljb 17086 Jan 12 19:56 myfile.i
-rw-rw-r-- 1 ljb ljb  1920 Jan 12 20:27 myfile.o
-rw-rw-r-- 1 ljb ljb   681 Jan 12 20:26 myfile.s

至此,我们就完成了一个程序翻译的完整过程,最终生成了可执行程序 myfile1 。

事实上,我们正常对一个程序进行编译,只需要执行 第四步:链接 的指令就可以,前三步都可省略,这里写出来是为了方便大家了解 gcc 编译的过程。

提示:

为了方便大家记住这些命令选项 与 后缀,我们可以来总结一下规律:预处理、编译、汇编的命令选项连起来是 -ESc ,与我们大家键盘最左上角的按键名字相同,而它们所生成的文件的后缀连起来是 .iso ,与镜像文件的后缀相同。这样是不是很好记忆了呢?

三、函数库

1、初识函数库

我们为什么能够在 Linux 下进行 C/C++ 代码的编写和编译呢?这是因为 Linux 系统默认已经携带了语言级别的 头文件 和 语言相应的库。

例如:我们的C程序中,并没有定义 "printf" 的函数实现,且在预编译中包含的 "stdio.h" 中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实 "printf" 函数的呢?最后的答案是:系统把这些函数实现都被做到名为 libc.so.6 的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径 "/usr/lib" 下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数 "printf" 了,而这也就是链接的作用。

这些库所处的位置是 /usr/include/ ,我们可以使用 ls 指令看一下:

是不是有许多我们熟悉的头文件名称呢?

ldd 指令:用于打印程序或者库文件所依赖的共享库列表。

ldd [可执行程序]

ldd 指令可以帮我们检测这个可执行程序被形成的时候,都依赖了哪些库:

这个就是函数库。库分为两种,静态库 与 动态库,库也是文件。

静态库 一般以 lib 开头,以**.a 结尾。形如 libXXXXX.**a 。

动态库 一般以 lib 开头,以**.so 结尾。形如 libXXXXX.**so 。

这种后缀划分是 Linux 下特有的。在 Windows 下,静态库的后缀是 .lib ,动态库的后缀是 .dll 。

我们区分一个库的名字,是去掉它的前缀与后缀,看中间部分。

我们看一下上面图片中用红框圈起来的库函数是什么名字:

所以该库的名字是 c - 2.17 ,是C语言的C标准库。


实际上,现在我们学习 Linux 时所用的指令,有很大一部分都是使用C语言写的。

以 ls 指令为例:

我们使用 which 指令找到 ls 指令所在的位置:

[ljb@iZuf69tfiox41j76yf0416Z lesson5]$ which ls
alias ls='ls --color=auto'
    /usr/bin/ls

再使用 ldd 指令查看它所依赖的函数库:

可以看到也是C标准库。

2、动静态库

2.1、动态库

动态库是专门让编译器对用户的程序进行动态链接的。动态库又被称为 共享库 ,在连接的时候,如果是动态链接,就找到动态库,拷贝用户所需要的代码的地址到用户自己的可执行程序中相关的位置。动态链接成功,用户的程序还是依赖动态库,一旦动态库缺失,用户的程序便无法运行,操作系统中绝大多数指令也都无法再运行了。

以上是动态库的缺点,那么它有什么优点呢?

因为动态库可以做到被大家共享,所以代码真正的实现永远都是在库中,程序内部只有地址,比较节省空间。

2.2、静态库

静态库是专门让编译器对用户的程序进行静态链接的。在连接的时候,如果是静态链接,就找到静态库,拷贝用户所需要的代码到用户自己的可执行程序中。静态链接成功,用户的程序不再依赖任何库,程序可以独立运行。

以上是静态库的优点,那么静态库有什么缺点呢?

使用静态库的话,因为程序自身拷贝代码的问题,会比较浪费空间。

2.3、动静态库的选择

Linux 下默认使用的是 动态链接 和 动态库。我们接下来就来证明一下。

我们使用 file 指令来查看可执行文件 myfile1 的文件类型:

ELF 是可执行程序的一种格式, dynamically linked 表明该可执行程序使用的是动态链接。


一般我们安装的操作系统,一般默认都只有动态库,没有安装静态库。那么如果我们想进行静态链接,需要先安装一下静态库:

sudo yum install -y glibc-static

安装完成后,我们就可以通过添加命令选项 -static 来使用静态链接了:

gcc [目标文件名] -o [生成文件名] -static

可以看到使用静态链接生成的可执行文件大小要比动态链接生成的大的多,这还仅仅只是一个非常简单的可执行程序,如果再复杂一点,这个差距会更大。因此我们选择使用动态链接

我们再来使用 ldd 指令 和 file 指令观察一下静态链接生成的可执行文件:

可以看到该文件没有依赖任何库,为静态链接。


关于 Linux 下编译器 gcc 与 函数库 的基本知识就讲到这里,希望同学们多多支持,如果有不对的地方欢迎大佬指正,谢谢!

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/weixin_74078718/article/details/128663291
版权归原作者 世间是否此山最高 所有, 如有侵权,请联系我们删除。

“【Linux】编译器 - gcc && 函数库”的评论:

还没有评论