【Linux】Linux编译器—gcc/g++的使用

该篇博客需要使用vim编译器,要是对其不熟悉,可以查看该篇博客vim的使用

一.背景

一个源文件生成可执行文件需要经过程序的翻译环境(将程序的文本文件翻译为机器可以识别的二进制文件),而翻译环境可以分为以下四个步骤:

  1. 预处理(头文件替换、条件编译、宏替换、去注释等)
  2. 编译(生成汇编语言)
  3. 汇编(生成机器可识别二进制文件,不可被执行,bin.obj)
  4. 链接(将我们自己形成的.obj文件和库文件进行某种合并,生成可执行文件)

具体可以看这篇博客程序环境

gcc/g++可以帮我们在LInux下生成C/C++的可执行文件,并通过选项分别执行到翻译环境的四个不同的步骤。
gcc编译C语言代码,g++编译C++代码,两者的使用方法相同,以下以gcc为例。

二.gcc如何生成

格式:gcc [选项] 要编译的文件 [选项] [目标文件]
我们先创建一个hello.c的测试文件,并使用vim编辑器编写代码

[YX@VM-16-5-centos lesson7]$ touch hello.c    //创建hello.c文件
[YX@VM-16-5-centos lesson7]$ vim hello.c      //使用vim编译器编译代码

在这里插入图片描述
我们可以直接使用gcc生成该测试文件的可执行文件
在这里插入图片描述
接着使用./a.out执行该可执行文件,就可以得到我们编写代码的结果
在这里插入图片描述
但当我们想要的到它在翻译环境下四个不同阶段生成的文件时我们该如何执行?

1.预处理(进行宏替换)

  • 预处理功能主要包括宏替换,文件包含,条件编译,去注释等。
  • 预处理指令是以#开头的代码行。
  • 实例:gcc -E hello.c(该指令为直接得到预处理后的内容,将其打印在显示器上,不方便查看,不做演示)
  • 实例:gcc -E hello.c -o hello.i(将预处理后的内容,放入.i文件中)
  • 选项"-E",该选项的作用是让 gcc 在预处理结束后停止编译过程。
  • 选项"-o"是指将前面执行的结果放入后面的文件,”.i“文件为已经过预处理的C原始程序。
  • 注意:文件名可以随便起,这个不影响,但最好遵循规则,将其写为.i文件,下面的文件同理

按照上面的指令,我们执行并生成预处理后的文件
在这里插入图片描述
注意:.i文件不是可执行文件,我们不能使用./hello.i执行该文件,但可以通过vim编辑器查看

最终可以看到该文件的内容

在这里插入图片描述
我们已经知道了如何查看预处理生成的文件,但上面的案例并没有完全展示预处理作用,这里我们先将测试文件修改后,在生成.i文件查看其结果,就可以明显的感受到预处理的作用,如下:

在这里插入图片描述

  • 对于.i文件,我们可以看到,它有860行,而.c文件,只有24行,.i文件中多余的代码就是被包含头文件的代码

2.编译(生成汇编)

  • 在这个阶段中,gcc首先要检查代码的规范性、是否有语法错误等,以确定代码实际要的工作,在检查无误后,gcc把代码翻译成汇编语言。
  • 用户可以使用”-S“选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。
  • 实例:gcc -S hello.c(直接将.c文件生成汇编后的内容,显示在显示器上,重新经历预处理,不建议这样使用)
  • 实例:gcc -S hello.i -o hello.s(该指令是将.i文件生成.s文件)
  • 实例:gcc -S hello.c -o hello.s(也可以直接将.c文件生成.s文件,重新经历预处理)
  • ”.s“文件为已经过编译的C原始程序

我们这里直接将.i文件生成.s文件

在这里插入图片描述

打开后可以清晰的看到它生成的汇编代码

在这里插入图片描述

3.汇编(生成机器可识别代码)

  • 汇编阶段是把编译阶段生成的”.s“文件转成目标文件。
  • 在此可以使用选项”-c“就可看到汇编代码已转化为”.o“的二进制目标代码了
  • 实例:gcc -c hello.c(同上,直接生成汇编后的数据,显示在显示器)
  • 实例:gcc -c hello.c -o hello.o(同上,从.c文件开始重新经过预处理、编译直到汇编)
  • 实例:gcc -c hello.s -o hello.o(由编译后的文件,生成汇编后的文件)

我们这里将.s文件生成汇编后的.o文件

在这里插入图片描述
打开后看到的是机器可以别的代码,我们看不懂的

在这里插入图片描述

4.链接(生成可执行文件或库文件)

  • 在成功编译之后,就进入了链接阶段。
  • 实例:gcc hello.c -o hello(从.c文件起经过翻译环境,直到链接)
  • 实例:gcc hello.o -o hello(由汇编生成的.o文件生成链接后的文件)
  • 链接后的文件是可执行文件,可以直接执行./hello,得到程序结果

我们使用.o文件生成链接后的文件

在这里插入图片描述

查看可执行文件中的内容

在这里插入图片描述
为机器识别的代码

问题

既然汇编和链接后生成的文件内容都是机器识别的代码,那么汇编后生成的文件是否可以执行?

我们先对其进行执行操作:
在这里插入图片描述
发现该文件没有可执行权限,给它加上,在执行:

在这里插入图片描述

结论: 即便一个文件具有可执行程序,它不是可执行文件就没有办法执行,同时证明.o文件无法执行

使用chmod -x hello.o取消它的可执行权限。

三.函数库

在链接阶段我们链接了库文件,那什么是库文件呢?这就要细细说来。

我们写的C/C++程序,在编译器上之所以可以运行,是因为我们的编译器将程序经过翻译环境变成可执行程序,最终输出结果。

我们写的程序中除了我们自己编写的代码,还要调用头文件中提供的函数,如我们经常使用的printf、scanf函数,就是在头文件stdio.h中声明的,这些头文件在Linux下存放在/usr/include目录及子目录下:

在这里插入图片描述
既然这些函数的声明是放在头文件中,那他们在哪里定义?

我们知道,翻译环境分为四个步骤,最后一个步骤链接的作用就是使汇编后生成的文件和库文件进行结合,而头文件声明的函数就存放在库文件中。

Linux系统下会把这些函数实现都做到名为libc.so.6的库文件中去了,在没有特别指点时,gcc会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去。这样就能实现函数“printf”或其他函数了,而这也就是链接的作用。

在这里插入图片描述
在比如我们看看上面写的测试文件hello.c的可执行文件a.out,看一下它链接的库文件,这里我们需要使用ldd指令查看。

在这里插入图片描述
实际上,在我们安装vs2019、vs2022这些编译器的时候,最重要的一个工作就是帮我们下载并安装语言的头文件和库文件。

所以我们可以得出结论,函数库本身就是文件,用来定义和存放函数。

1.函数库的分类

函数库分为两种,静态库和动态库,它们的区别如下:

(1)动态库

命名:libXXXXX.so

其中XXXXX可以使任意名字,如上面所使用的库文件libc.so.6,就是一个动态库。去掉前缀lib和后缀.so剩余c表面它是由c语言编写的库,而剩余的.6表示版本号。

动态库的作用:动态链接库文件,在链接阶段,拷贝动态库中我们需要的代码的地址到我们自己的可执行程序中的相关位置,在运行程序时通过地址调用动态库中函数,保证代码的正常运行。

我们编写的代码在链接时,如果没有指定,默认链接的都是动态库。

(2)静态库

命名:libXXXXX.a

如上,lib为前缀,.a为后缀,中间可以是任意名

静态库的作用:静态链接库文件,在编译链接时,把静态库中我们所需要的代码拷贝到可执行文件中,因此生成的文件比较大,但在运行时也就不在需要库文件了。

比如:我们写程序时,printf函数可能要出现不止一次,那么每个printf函数都会被替换为静态库中对应的代码,这样就使文件变大,多次替换的printf函数浪费了空间。

静态链接需要我们自己链接,链接方法如下:

  1. 我们可以通过file指令查看可执行文件链接的是动态库或静态库,或是使用ldd指令查看链接库的命名,根据命名判断
    在这里插入图片描述

  2. 通过指令gcc hello.c -o hello-static -static使生成的hello-static可执行文件静态链接在这里插入图片描述

    • 静态链接和动态链接后的文件相差10倍左右十分正常
  3. 我使用的是云服务器,默认只有动态库,我们需要自己手动安装静态库才可以使用,方法如下:

     sudo yum install -y glibc-static   //在普通用户下安装c语言静态库
    yum install -y glibc-static        //在root用户下安装c语言静态库
    
    sudo yum install -y glibc-static libstdc++-static    //在普通用户下安装c++静态库
    yum install -y glibc-static libstdc++-static    //在root用户下安装c++静态库
    

2.区别

动态库通过程序中动态库代码对应的地址调用动态库中对应的函数完成代码的执行,如果动态库突然消失,可执行程序将无法执行。

静态库通过函数名生成的地址,在库中找到对应的函数,将其拷贝到可执行文件中,在文件中调用,这使得可执行文件即使没有静态库也可以执行代码,但随着拷贝,可执行文件也会变大。

3.拓展

我们在Linux中使用的指令,都是使用c语言编写的,我们可以查看不同指令的链接库,看是否使用c语言编写,如下:

查看ls指令的库文件:

在这里插入图片描述
查看which指令的库文件:

在这里插入图片描述

所以指令就是程序。

  • 关于函数库的知识该篇博客就只有这些了,更多的内容会在基础I/O中讲解。

四.记忆

1.选项

我们在进行翻译环境的四个步骤中,都使用了-o选项,而链接只使用了-o,剩余三个步骤我们可以通过键盘左上角的Esc按钮来记忆。

预处理——-E,编译——-S,汇编——c

我们要是忘记了只需要看键盘左上角的Esc按钮,但注意,ES是大写的。

2.后缀

链接生成的文件没有后缀不需要记忆,其余三个按顺序刚好对应镜像文件的后缀.iso

五.gcc选项

gcc的选项中最常用的就是上面翻译环境四个步骤的各个选项,将其汇总并和其他的一些选项一起放在这里,方便大家查看。

  • -E 只激活预处理,这个不生产文件,需要把它重定向到一个输出文件里面。
  • -S 编译到汇编语言不进行汇编和链接。
  • -c 编译到目标代码。
  • -o 文件输出到文件。
  • -static 此选项对生成的文件采用静态链接。
  • -g 生成调试信息。GNU 调试器可利用该消息。
  • -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库。
  • -O 0
  • -O 1
  • -O 2
  • -O 3 编译器的优化选项的4各级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高。
  • -w 不生成如何警告信息。
  • -Wall 生成所有警告信息。

猜你喜欢

转载自blog.csdn.net/m0_52094687/article/details/128608256