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

简介: Linux编译器gcc/g++的使用。

1. 什么是gcc/g++

Linux中的gcc是由GNU推出的一款功能强大的、性能优越的多平台编译器。gcc编译器能将C、C++语言源程序和目标程序编译、连接成可执行文件。

那么gcc和g++的区别又是什么呢?只要是 GCC 支持编译的程序代码,都可以使用 gcc 命令完成编译。可以这样理解,gcc 是 GCC 编译器的通用编译指令,因为根据程序文件的后缀名,gcc 指令可以自行判断出当前程序所用编程语言的类别。

但如果使用 g++ 指令,则无论目标文件的后缀名是什么,该指令都一律按照编译 C++ 代码的方式编译该文件。也就是说,对于 .c 文件来说,gcc 指令以 C 语言代码对待,而 g++ 指令会以 C++ 代码对待。但对于 .cpp 文件来说,gcc 和 g++ 都会以 C++ 代码的方式编译。


2. 程序翻译的四个阶段

我们知道一个C语言程序要想被执行,就需要经过两个环境。第一个是翻译环境,这个环境下主要将我们的源代码转换成可执行的机器指令。第二个是执行环境,这个环境是要将我们翻译环境所生成的可执行程序在运行环境下输出我们想要的结果。

这里我们先来大概看一下程序翻译环境下所执行的四个阶段 ==预处理,编译,汇编,链接== 的过程都会发生什么:

image.png

接下来我们就通过在Linux环境下进行测试程序翻译环境下的四个过程:预处理,编译,汇编,链接

2.1 预处理

在预处理的过程中我们的程序会有以下变化: 头文件的展开、条件编译、宏的替换和注释的删除等操作。

下面我们就在Linux环境下通过一个简单的C语言程序来演示这个过程:

#include<stdio.h>

#define N 666

#define CJL

int main()
{
   
   
      printf("hello world hello world hello world hello world\n");
      /*printf("hello world\n");
      printf("hello world\n");
      printf("hello world\n");
      printf("hello world\n");
      printf("hello world\n");
      printf("hello world\n");
      printf("hello world\n");
      printf("hello world\n");*/

#ifdef CJL
      printf("hello CJL\n");
#else
      printf("hello world\n");
#endif
      return 0;
}

下面我们来演示预处理这个阶段程序所发生的变化

命令:gcc –E test.c –o test.i

  • ==gcc==:表示用gcc这款编译器来编译test.c这个C语言程序
  • 选项 ==-E==:该选项的作用是让 gcc 在预处理结束后停止编译过程。
  • 选项 ==-o==:是指目标文件,.i为后缀的文件为已经过预处理的C原始程序。

image.png

这里我们可以看到当前目录下已经生成了.i为后缀的目标文件。下面我们用vim编辑器来对比一下.i文件和.c文件中的内容:

image.png

这里我们可以看到原来文件中的注释已经消失不见了。同时文件的内容也增加了很多很多,这是因为头文件中的内容已经被展开到test.i这个文件中了,同时,条件编译的内容也已经被替换成了要执行的语句了。


2.2 编译

编译的过程其实是将我们的==C语言程序翻译成汇编语言==的过程。在这个阶段中,gcc 首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,gcc 把代码翻译成汇编语言。

在这里我们可以使用 ‘’-S‘’ 选项来进行查看,该选项只进行编译而不进行汇编,生成汇编代码。我们可以直接使用在预处理后生成的.i文件来进行操作。

命令:gcc -S test.i -o test.s

image.png

我们需要注意的是:编译产生的文件一般以.s为后缀,下面我们来对比一下生成的.s文件和.i文件中的内容。

image.png

这里我们可以看到原来的.i文件中的C语言程序已经被转换成了对应的汇编代码。


2.3 汇编

汇编这个过程所执行的内容主要是将我们的.s文件中的汇编代码转换成可重定位的目标二进制文件。

和上面的过程一样我们接着使用编译后的.s文件来执行汇编这个操作,这里我们使用的是 “-c” 选项来执行我们的汇编过程。

命令:gcc -c test.s -o test.o

image.png

下面我们来看一下.o文件中的内容,发现完全看不懂。

image.png

这里我们利用 od 指令来看一下文件中的二进制指令

image.png


2.4 链接

链接过程所做的事情就是将我们汇编过程中形成的可重定位的目标二进制文件和C语言中的库文件合并,最终形成可执行程序。

命令:gcc test.o -o Test

如果我们不使用-o选项来指定文件生成的名字时,生成的默认文件的名字就是a.out,这里我们依然和上面保持一致,自己来命名生成的文件的名称。

image.png

这里就生成了一个名字为Test的可执行程序。当我们直接执行它时就可以生成我们最终想要的结果:

image.png


3. 函数库

在这里涉及到一个重要的概念:函数库

我们的C程序中,并没有定义“printf”的函数实现,且在预编译中包含的“stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实“printf”函数的呢?

其实这是因为系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时,gcc 会到系统默认的搜索路径“/usr/lib”下进行查找,也就是链接到 libc.so.6 库函数中去,这样就能实现函数“printf”了,而这也就是链接的作用。其实链接的本质就是如何将调用的库函数和标准的库关联起来。


3.1 动态库

💖 动态库:

动态库在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时==由运行时链接文件加载库,这样可以节省系统的开销==。动态库一般后缀名为“so”,如前面所述的libc.so.6 就是动态库gcc 在编译时默认使用动态库。完成了链接之后,gcc 就可以生成可执行文件,这点可以通过 file 命令验证。

💕 动态链接

链接的时候,如果是动态链接,找到动态库,然后拷贝动态库中我们需要的代码的地址到我们自己的可执行程序中相关的位置。

image.png

ldd命令查看依赖的动态库列表

image.png

这里我们需要补充一点知识:

库的本质也是文件,静态库的格式一般是:libxxxxxx.a,静态库的前缀是以lib为前缀,.a为后缀。中间的xxxxxx才是库的名称;而动态库的一般格式则是:libxxxxxx.so,动态库的前缀是以lib为前缀,.so为后缀。中间的xxxxxx是库的名称。

这里我们可以看到gcc编译得到的可执行程序是通过动态链接来链接C动态库的。


3.1 静态库

💖 静态库:

静态库是指编译链接时,把库文件的代码全部加入到可执行文件中,因此生成的文件比较大,但在运行时也就不再需要库文件了。其后缀名一般为“.a”。

💕 静态链接

链接的时候如果是静态链接,找到静态库,然后拷贝静态库中我们需要的代码的到我们自己的可执行程序中相关的位置。

与动态库不同的是:C和C++静态库需要我们自己去安装。

sudo yum install -y glibc-static
sudo yum install -y libstdc++-static

当我们安装完成后就可以对我们的程序进行静态链接了。

命令:gcc 源文件 -o 目标文件 -static

image.png

这里我们可以看到静态链接的方式最终链接形成的可执行程序所占用的内存非常大。这是因为静态链接直接拷贝了静态库中的代码到我们的可执行程序中了。

下面让我们对比一下静态库/静态链接和动态库/动态链接的优缺点:

  • 静态链接成功,我们的程序不需要依赖任何库,自己就可以独立运行。
  • 动态链接成功,我们的程序还是需要依赖动态库,一旦动态库缺失,我们的程序便无法运行。
  • 静态库由于是自生拷贝的问题,所以比较浪费空间。
  • 动态库因为可以做到被大家所共享方法,所以真正的实现永远都是在库中。程序内部只有地址,比较节省空间。
  • 静态库VS动态库:Linux默认使用的是动态链接和动态库。

4. gcc/g++的使用

首先我们需要安装一下gcc和g++:

sudo yum install -y gcc
sudo yum install -y gcc-c++ libstdc++-devel

当然这里还有一些gcc和g++常用的选项:

  • -E 只激活预处理,这个不生成文件,你需要把它重定向到一个输出文件里面
  • -S 编译到汇编语言不进行汇编和链接
  • -c 编译到目标代码
  • -o 文件输出到 文件
  • -static 此选项对生成的文件采用静态链接
  • -g 生成调试信息。GNU 调试器可利用该信息。
  • -shared 此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库.
  • -O0
  • -O1
  • -O2
  • -O3 编译器的优化选项的4个级别,-O0表示没有优化,-O1为缺省值,-O3优化级别最高
  • -w 不生成任何警告信息。
  • -Wall 生成所有警告信息
相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
2月前
|
Linux 编译器 C语言
【Linux快速入门(一)】Linux与ROS学习之编译基础(gcc编译)
【Linux快速入门(一)】Linux与ROS学习之编译基础(gcc编译)
|
4月前
|
Linux C语言
成功解决 在Linux CentOS 7 中安装gcc
这篇文章介绍了如何在Linux CentOS 7系统中安装gcc (g++) 8工具集。由于CentOS 7默认的gcc版本是4.8,而这个版本与Qt 5.14、Qt 5.15或更高版本不兼容,可能会导致编译时出现系统头文件错误。文章中提到,即使在项目配置中添加了`CONFIG+=c++11`,如果仍然报错,那么很可能是gcc版本的问题。为了解决这个问题,文章提供了使用CentOS的Software Collections (scl)来安装更新版本的gcc的步骤。
成功解决 在Linux CentOS 7 中安装gcc
|
3月前
|
Linux 编译器 C语言
Linux内核对GCC版本的检测
Linux内核对GCC版本的检测
|
4月前
|
Java Linux 编译器
【Linux】gcc简介+编译过程
【Linux】gcc简介+编译过程
114 0
|
6月前
|
Java 编译器 Linux
程序技术好文:详解Linux安装GCC方法
程序技术好文:详解Linux安装GCC方法
206 0
|
6月前
|
NoSQL 编译器 Linux
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
101 0
|
1月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
92 8
|
1月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
251 6
|
1月前
|
Linux
在 Linux 系统中,“cd”命令用于切换当前工作目录
在 Linux 系统中,“cd”命令用于切换当前工作目录。本文详细介绍了“cd”命令的基本用法和常见技巧,包括使用“.”、“..”、“~”、绝对路径和相对路径,以及快速切换到上一次工作目录等。此外,还探讨了高级技巧,如使用通配符、结合其他命令、在脚本中使用,以及实际应用案例,帮助读者提高工作效率。
80 3
下一篇
DataWorks