Linux:编译器 - gcc

简介: Linux:编译器 - gcc

gcc概述

GCC(英文全拼:GNU Compiler Collection)是 GNU 工具链的主要组成部分,是一套以 GPL 和 LGPL 许可证发布的程序语言编译器自由软件,由 Richard Stallman 于 1985 年开始开发。

gcc是GCC中的C语言编译器,而g++是GCC中的C++编译器。本博客只讲解gcc,g++的语法和选项和gcc都是一致的。

gcc编译C语言最基本的语法:

gcc test.c

你就完成了对test.c文件的编译操作,该过程会默认生成一个名为a.out的可执行文件,你可以直接运行a.out

如果你想要编译的同时,指定编译后生成文件的名称,就加上-o选项,后紧跟着编译后文件的名称:

gcc test.c -o test.out

此时生成的可执行文件就叫做test.out了。

gcc默认不支持C99标准,如果你希望以C99标准编译该文件,加上选项-std=c99

gcc test.c -o test.out -std=c99

编译C语言要经过预处理编译汇编链接的过程,可是为什么我们需要这些过程呢?这涉及到计算机语言的发展史了。


语言发展史

在最早的时候,对计算机编程是通过打孔纸带的,如果有孔就是1,没孔就是0。但是这种二进制编程的效率太低了,于是计算机的从业者就开始对二进制进行改进,诞生了汇编语言。汇编语言的诞生具有划时代的意义,于是就发展出了操作系统,编译器等东西。比如Unix的最初版,就是通过汇编语言写的。后来为了方便,又诞生了面向过程语言,其中最大名鼎鼎的毫无疑问是C语言。再后来人们觉得面向对象思想是符合人类的编程习惯的,就又有了C++,python,Java这样的高级语言。

现在问题就来了,不论是汇编语言,C语言,还是如今的高级语言。是先有语言,还是先有编译器?这就涉及到编译器自举过程。

编译器自举

在出现汇编语言之前,我们只有二进制语言来编程。但是人们发明汇编语言后,就要有对应的编译器来编译汇编语言,不然计算机就无法理解汇编语言。因为汇编语言无法被编译,所以我们就要先用二进制语言写一个汇编语言的编译器,然后我们的汇编语言就可以被计算机理解了。但是一旦我们的汇编语言可以被解释了,可以用于编程了,那么我就可以再用汇编语言写一个编译器了。从此以后,编译器就用汇编语言再来书写了。这个过程就叫做编译器自举。


同理,在C语言出来之前,我们无法用C语言写一个C语言的编译器,所以就用汇编语言写一个C语言的编译器。现在C语言就可以被计算机编译了,于是我们就可以再用C语言写一个C语言的编译器。


那么现在又有一个问题了,请问我们在解释C语言的时候,是直接把C语言解释为二进制指令,让计算机理解。还是先把C语言解释为汇编语言,然后再让汇编语言解释为二进制呢?不得不说,如果直接让C语言变成二进制语言,这个过程未免太麻烦了,二进制语言过于反人类了,相应的汇编语言就好很多。我们要在巨人的肩膀上看世界,汇编语言已经把转换为二进制这个工作做完了,那么我们只需要在汇编语言的基础上改进就好。因此C语言要先变成汇编语言,最后变成二进制。这就是为什么C语言要有预处理,编译,汇编的过程。


gcc的编译过程

现在我们再简单回顾一下以上三个阶段的功能,并且讲解gcc的编译相关选项。

test.c中,有如下代码:


预处理

  1. 头文件展开
  2. 去注释
  3. 宏替换
  4. 条件编译

那么经过这个过程,还是C语言吗?答案是是的,该过程只是预先处理了一下C语言,把一些没必要的内容删除,减少后续工作的工作量。处理后依然是C语言代码。

使用gcc时,带上-E选项,就可以得到预处理后的文件:

gcc -E test.c -o test.i

此处我把编译后的文件命名为test.i,一般而言经过预处理的文件都以.i为后缀

左侧是预处理后的文件test.i,右侧是源文件test.c

  1. 可以看到,左侧代码经过了800多行才到main函数,这800行就是<stdio.h>头文件的展开
  2. 语句#define M 100没有了,并且main函数中的M被替换为了100,也就是完成了宏替换
  3. 被注释的四行//printf("hello world!\n");被删除了,也就是注释的删除

现在再看看条件编译,我把代码改为以下代码:

如果我们把VERSION2注释掉,那么就会输出"hello VERSION1.0";如果把VERSION1注释掉,那么就会输出"hello VERSION2.0";如果都注释掉,那么就输出"hello free"。我们确实可以在编写代码的时候,通过注释来进行条件编译,控制代码的版本。但是这个过程在编译的时候就可以直接控制。gcc可以在命令行中定义宏。

语法:

gcc test.c -o test1.exe -D VERSION1=1

以上命令中,-D VERSION=1就相当于在代码中写了一句#define VERSION1 1-D选项用于指定一个宏。

看到以下现象:

第一次-D VERSION=1,最后一行输出的hello VERSION1.0;第二次-D VERSION=2,最后一行输出hello VERSION2.0;第三次没有加任何宏定义,就输出了hello free

也就是说我们可以通过指令给编译器传递不同的宏,来动态裁剪代码

比如说visual studio有社区版和专业版,社区版是免费的,专业版要收费,毫无疑问社区版是专业版经过一定阉割后产生的。那么在微软公司内部,这两个版本要分开维护吗?这样的话如果一个版本出现了问题,那还要去看看另外一个版本有没有相同的问题,然后修改两份代码,未免太麻烦了。因此完全可以采用条件编译,把需要被阉割掉的部分用条件编译包起来。最后在编译的时候,使用命令行传入不同的宏,实现一份代码两个版本。因此条件编译的最重要的应用场景就是一份代码发行多个版本的软件。


编译

该过程是把C语言的代码变成汇编语言的过程。

想要生成该阶段的文件,需要通过-S选项:

gcc -S test.c -o test.s

该阶段的文件,一般以.s结尾。

test.s内部:

可以看到,内部确实已经变成汇编语言的文件了。


汇编

该过程是把汇编语言变成二进制的过程,这个过程生成的文件叫做目标文件,全称为可重定位目标二进制文件。这个文件虽然已经是二进制文件了,但是它还不是一个可执行文件

想要获得该阶段的文件,需要使用选项-c

gcc -c test.c -o test.o

一般而言,该阶段的文件以.o或者.obj结尾。在Linux中习惯用.o,在Windows中习惯用.obj.

test.o内部:

可以看到,其内部都是一些乱码,因为我是用的vim是一款文本编辑器,其不能解析二进制文本,所以会出现乱码。

为什么该过程已经是一个二进制文件了,还是不能被计算机执行?计算机不是可以识别二进制吗?

这是因为缺少链接的过程,接下来我们看看链接都有哪些功能。


gcc的链接过程

我们的C语言内部,调用很多库函数,比如printf,scanf等等。它们并没有在编译的时候展开,不信你可以回去看看那个.i文件,绝对没有展开一个叫做printf的函数。那么C语言要如何拿到这个函数,并调用它呢?这就涉及到链接的过程。


如果你想要让你的.c,.i,.s,.o中的任意一个文件变成链接后的文件,不用带任何选项,直接执行gcc即可,因为直接执行就是生成可执行文件,这已经是链接后的文件了。

gcc -o test.exe test.c

这样最后生成的test.exe就是可执行文件了,在此.exe不是强制的,只是我前面已经写过太多test命名的文件了,使用后缀区分一下属性。在Windows中,可执行文件的后缀就是.exe,因为Windows通过后缀区分文件,而Linux不通过后缀区分文件,因此这个.exe可有可无,只用于帮助我们自己快速判断文件属性。

那么这个可执行文件test.exe是如何链接到库的,又链接了那些库呢?

我们可以通过ldd指令来查看一个可执行文件链接了那些库:

ldd test.exe

输出结果:

比如第二行的libc就是C语言的标准库。另外的,它还指明了一些库在系统中的路径。也就是说我们的很多头文件,都已经早早地在Linux中下载好了,因此我们可以在Linux上运行C语言代码。

比如说/usr/include/路径下的文件:

你可以看到很多非常非常熟悉的头文件,比如<stdio.h><math.h><stdlib.h>等等。

在链接到库时,库分为两种:动态库静态库


动态库与静态库

  • 静态库:编译链接时,把库文件的代码全部拷贝到可执行文件中,在运行时也就不再需要库文件了。

优点:只要形成了可执行文件,那么就脱离对库的依赖,可以自主运行,可移植性好

缺点:相同的资源拷贝多份,浪费资源,生成的文件比较大

  • 动态库:在编译链接时并没有把库文件的代码加入到可执行文件中,而是在程序执行时由运行时链接文件加载库,编译器会提供动态库的地址,当程序执行到指定的代码段,就会去动态库内部查找对应的内容。

优点:节省资源,整个操作系统所有使用动态库的程序,只需要一份库文件,内存中也只加载一份

缺点:一旦丢失,所有链接该库的程序都无法执行了

通过动态库实现的链接,叫做动态链接,通过静态库实现的链接叫做静态链接。这里有一个注意点,那就是:虽然动态库和静态库内部的函数是一样的,但是动态库和静态库是两份不同的文件,并不是说把动态库拷贝一份到代码中,就是静态链接了。这涉及到一些更深的知识,就不在gcc的章节中介绍了。

动静态库的后缀

Linux中,动态库以.so为后缀,静态库以.a为后缀

Windows中,动态库以.dll为后缀,静态库以.lib为后缀

我们直接以一般的形式编译一个文件:

gcc -o test1.exe test.c

就可以得到一个名为test1.exe的文件。先通过ldd查看相关的库:

可以看到,每个库中都包含了.so的字段,说明gcc在编译时默认使用动态库

我们也可以通过file指令查看,执行file test1.exe

dynamically linked字段就表示这是一个动态链接的程序。

如果我们想要生成静态链接的文件,则额外加上选项-static

gcc -o test2.exe test.c -static

此时test2.exe文件就是一个静态链接的文件了。

但是你大概率无法执行该命令,会出现以下报错:

这是因为Linux默认是不带静态库的,要手动安装:

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

该指令需要root权限,要么sudo,要么以root身份执行。其中glibc-static是C语言静态库,libstdc++-static是C++静态库。

先对比一下两个文件的大小:

可以看到,两个文件之间差不多相差了100倍的大小,因此静态库非常浪费资源。

使用ldd

其很明确的告诉你,这不是一个动态链接的可执行文件not a dynamic executeable

再用file

statically linked字段说明这是一个静态链接的可执行文件。

相关文章
|
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简介+编译过程
104 0
|
6月前
|
Shell Linux 网络安全
Linux怎样在使用ssh 链接时就指定gcc 的版本
Linux怎样在使用ssh 链接时就指定gcc 的版本
60 7
|
6月前
|
自然语言处理 安全 编译器
深入探索Linux下的luac命令:Lua编译器的奥秘
**探索Linux上的`luac`:Lua编译器详解** `luac`是Lua源代码的编译器,将`.lua`转换为字节码`.luac`,提升执行效率。它通过词法、语法分析生成可移植、安全的字节码。主要特点包括高效、可移植和安全。命令参数如`-l`列出字节码结构,`-o`指定输出,`-s`去除调试信息。使用示例:`luac -o script.luac script.lua`,然后用`lua script.luac`执行。注意版本兼容性,开发时保留调试信息,生产环境优化源代码并备份源文件。
|
6月前
|
Linux vr&ar C语言
Linux怎样更新Centos下Gcc版本支持C17?Centos7快速安装gcc8.3.1 可支持C++17(附gcc相关链接整理)
Linux怎样更新Centos下Gcc版本支持C17?Centos7快速安装gcc8.3.1 可支持C++17(附gcc相关链接整理)
346 2
|
6月前
|
Ubuntu Linux 编译器
当自身需要使用的 gcc版本 和Linux 默认版本 存在大版本差异时怎样处理
当自身需要使用的 gcc版本 和Linux 默认版本 存在大版本差异时怎样处理
142 2
|
6月前
|
Java 编译器 Linux
程序技术好文:详解Linux安装GCC方法
程序技术好文:详解Linux安装GCC方法
194 0
|
6月前
|
NoSQL 编译器 Linux
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
【Linux】--- Linux编译器-gcc/g++、调试器-gdb、项目自动化构建工具-make/Makefile 使用
92 0