START
hi,大家好!
今天带大家了解一下GCC。
首先说一句:大写的GCC和小写的gcc不是一个东西呦,下面我们慢慢道来...
1. GCC是什么?
GNU Compiler Collection (GCC)是GNU项目开发的编译工具集,支持各种编程语言、硬件架构和操作系统。自由软件基金会(FSF)根据GNU通用公共许可证(GNU GPL)将GCC作为免费软件发布。GCC是GNU工具链的关键组成部分,也是与GNU和Linux内核相关的大多数项目的标准编译器。GCC在2019年拥有大约1500万行代码,可见功能支持不是一般的全呀,且是现存最大的免费程序之一。作为工具,它在自由软件的发展中发挥了重要作用。
早期GCC的全拼为GNU C Compiler,最初GCC的定位只用于编译C语言。但经过不断的迭代,GCC的功能得到了很大的扩展,它不仅可以用来编译C语言程序,还可以处理C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言等多种编译语言编写的程序。
GCC官网:https://gcc.gnu.org,最新的版本目前已到了:GCC 12.2 (changes)
特点
gcc是一个可移植的编译器,支持多种硬件平台。例如ARM、X86。
gcc它还能跨平台交叉编译。本地编译器,是指编译出来的程序只能够在本地环境进行运行。而gcc编译出来的程序能够在其他平台进行运行。例如嵌入式程序可在x86上编译,然后在arm上运行。
gcc支持多种语言,用于解析不同的语言。
gcc是按模块化设计的,可以加入新语言和新CPU架构的支持。
gcc是自由软件。任何人都可以使用或更改这个软件。
本文着重讲解Linux下GCC常用的重要知识点
GCC与gcc、g++
GCC即GNU编译工具集,有编译器、链接器、组装器等,主要用来编译C 和 C++ 语言,也可以编译 Objective-C 和 Objective-C++ 程序。
gcc(GNU C Compiler)代表的是GNU C语言编译器;g++代表的是GNU C++语言编译器。但是从本质上讲,gcc 和g++并不是真正的编译器,它们只是GCC里面的两个工具,在编译C/C++程序时,调用真正的编译器对代码进行编译。可以简单地这样理解:gcc工作的时候会调用C编译器;g++工作的时候会调用C++ 编译器。
实际使用中我们习惯使用gcc指令编译C语言程序,用g++指令编译C++代码。需要说的是,gcc指令也可以用来编译C++程序,同样g++指令也可以用于编译C语言程序。
gcc与g++区别
gcc将.c的文件当作C程序,将.cpp的文件当作C++程序
g++将.c和.cpp的文件都当成C++程序
链接方式:gcc不会自动链接C++的库(比如 STL 标准库),而g++会自动链接C++库
预处理器宏:g++会自动添加一些预处理器宏,比如 __cplusplus,但是gcc不会;
gcc是GCC编译器的通用编译指令,根据文件的后缀名gcc指令可以自行判断出当前程序所用编程语言的类别:
.c:默认以编译C语言程序的方式编译
.cpp:默认以编译C++程序的方式编译
.m:默认以编译Objective-C程序的方式编译
.go:默认以编译Go语言程序的方式编译
当然,我们也可手动指定:-x
gcc -xc file.c 表示以编译C语言代码的方式编译file.c文件
gcc -xc++ file.cpp 则表示以编译C++代码的方式编译file.cpp文件
关于gcc和g++指令,有其它更多细节方面的区别,在平常编译程序时我们往往坚持一下原则:
对于C语言程序的编译,我们应该使用gcc指令
编译 C++ 程序则推荐使用 g++ 指令
开发纯C语言的程序,可以使用gcc;如果是C/C++混合编程,建议使用g++。
2. GCC编译
我们知道计算机只识别机器语言,而我们编写的源代码要想让计算机识别必须翻译成机器语言,也就是生成计算机上识别的可执行的文件,而GCC是如何将我们编写的源代码编译成最终的可执行文件的?
源文件(Source File): 就是我们编写好的源代码文件,源文件本质上是纯文本文件,它的内部并没有特殊格式。我们可以通过源文件的后缀指明该文件中保存的是何种语言的代码,这样程序员更加容易区分,编译器也更加容易识别,它并不会导致该文件的内部格式发生改变。
编译(Compile): 程序语言代码是程序员理解和识别的语言,但是机器只识别机器语言,计算机只认识执行二进制形式的指令,所以需要一个工具,将程序语言代码转换成计算机能够识别的二进制指令,这个工具是一个特殊的软件,叫做编译器(Compiler)。编译器能够识别代码中的句子以及各种特定的格式,并将他们转换成计算机能够识别的二进制形式,这个过程称为编译(Compile)。编译也可以理解为“翻译”,类似于将英文翻译成中文,它是一个复杂的过程,我们不必关心编译器编译时内部的复杂过程。
链接(Link): C语言代码经过编译以后,并没有生成最终的可执行文件,而是生成了一种叫做目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是一样的。对于Visual C++,目标文件的后缀是.obj;对于GCC,目标文件的后缀是.o。目标文件经过链接(Link)以后才能变成可执行文件。既然目标文件和可执行文件的格式是一样的,为什么还要再链接一次呢,直接作为可执行文件不行吗?不行的!因为编译只是将我们自己写的代码变成了二进制形式,它还需要和系统组件(比如标准库、动态链接库等)结合起来,这些组件都是程序运行所必须的。链接(Link)其实就是一个“打包”的过程,它将所有二进制形式的目标文件和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)。
GCC支持的后缀文件
后缀文件类型.cC语言源代码文件.a由目标文件构成的档案库文件.C/.cc/.cxxC++源代码文件.h程序所包含的头文件.i预处理过的C源代码文件.ii预处理过的C++源代码文件.mObjective-C源代码文件.o编译后的目标文件.s汇编语言源代码文件.S预编译的汇编语言源代码文件
GCC的编译过程可以划分为四个阶段:预处理、编译、汇编、链接。
预处理(Pre-Processing):主要处理以“#”开头的命令,生成.i/.ii文件
编译 (Compiling):将.c .i等文件翻译成汇编代码,生成.s/.S文件
汇编 (Assembling):将汇编代码翻译成机器代码,生成.o文件
链接 (Linking):将生成的多个目标文件(.o文件)连接起来,生成可执行文件
下面我们通过一个简单例程来分析GCC的整个执行过程:
#include <stdio.h>
int main(int argc, char const *argv[])
{
/* code */
printf("hello GCC\n");
return 0;
}
1.预处理阶段
gcc -E main.c -o main.i
预处理过程主要处理源代码中以#开头的预编译指令,处理#include和#define,它把#include包含进来的.h文件插入到#include所在的位置,把源程序中使用到的用#define定义的宏用实际的字符串代替
主要处理如下:
将所有的#define宏定义删除,并且展开所有的宏定义
处理所有条件编译指令,如#if,#ifdef等
处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件
删除所有的注释//和 /**/
添加行号和文件标识,如#2 “main.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息
保留所有的#pragma编译器指令,因为编译器须要使用它们
2.编译阶段
gcc -S main.i -o main.s
编译阶段,在这个阶段中,GCC首先要检查代码的规范性、是否有语法错误等,以确定代码的实际要做的工作,在检查无误后,GCC把代码翻译成汇编语言。
让我们来看一下生成的汇编文件main.s的内容:
3.汇编阶段
gcc -c main.s -o main.o
汇编阶段把*.s文件翻译成二进制机器指令文件*.o,也就是说将汇编代码转变成机器可以执行的命令
4.链接阶段
gcc main.o -o main
链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。通过本例程通俗的说就是:在这个程序中并没有定义”printf”的函数实现,且在预编译中包含进的”stdio.h”中也只有该函数的声明,而没有定义函数的实现,那么,是在哪里实现”printf”函数的呢?答案是系统把这些函数实现都被做到名为libc.so.6的库文件中去了,在没有特别指定时,GCC会到系统默认的搜索路径”/usr/lib”下进行查找,也就是链接到libc.so.6库函数中去,这样就能实现函数”printf”了,而这也就是链接的作用
默认情况下, GCC在链接时优先使用动态链接库,只有当动态链接库不存在时才考虑使用静态链接库,如果需要使用静态链接库可以在编译时加上-static选项,强制使用静态链接库。由于动态库节省空间,linux下进行链接的缺省操作是首先连接动态库。
一般头文件或库文件的位置在:
/usr/include及其子目录下的include文件夹
/usr/local/include及其子目录下的include文件夹
/usr/lib
/usr/local/lib
/lib
静态库链接时搜索路径顺序:
ld会去找GCC命令中的参数-L
再找gcc的环境变量LIBRARY_PATH
再找内定目录 /lib /usr/lib /usr/local/lib
动态链接时、执行时搜索路径顺序:
编译目标代码时指定的动态库搜索路径
环境变量LD_LIBRARY_PATH指定的动态库搜索路径
配置文件/etc/ld.so.conf中指定的动态库搜索路径
默认的动态库搜索路径/lib
默认的动态库搜索路径/usr/lib
库的搜索路径遵循几个搜索原则:从左到右搜索-I -l指定的目录,如果在这些目录中找不到,那么GCC会从由环境变量指定的目录进行查找。头文件的环境变量是C_INCLUDE_PATH,库的环境变量是LIBRARY_PATH,如果还是找不到,那么会从系统指定指定的目录进行搜索。
相关环境变量:LIBRARY_PATH环境变量:指定程序静态链接库文件搜索路径 LD_LIBRARY_PATH环境变量:指定程序动态链接库文件搜索路径
通过链接阶段后就生成了我们需要的可执行程序,我们就可以执行可执行程序了
GCC参数说明
GCC编译时参数众多,一下是常用的参数说明:
-C
在预处理的时候, 不删除注释信息, 一般和-E使用, 有时候分析程序,用这个很方便的。
-M
生成文件关联的信息。包含目标文件所依赖的所有源代码你可以用 gcc -M hello.c 来测试一下,很简单。
-MM
和上面的那个一样,但是它将忽略由 #include<file> 造成的依赖关系。
-MD
和-M相同,但是输出将导入到.d的文件里面
-MMD
和 -MM 相同,但是输出将导入到 .d 的文件里面。
-Wa,option
此选项传递 option 给汇编程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会汇编程序。
-Wl.option
此选项传递 option 给连接程序; 如果 option 中间有逗号, 就将 option 分成多个选项, 然 后传递给会连接程序。
-llibrary
指定编译的时候使用的库
例子用法:
gcc -lcurses hello.c使用 ncurses 库编译程序
-Ldir
制定编译的时候,搜索库的路径。比如你自己的库,可以用它制定目录,不然编译器将只在标准库的目录找。这个dir就是目录的名称。
-O0 、-O1 、-O2 、-O3
编译器的优化选项的 4 个级别,-O0 表示没有优化, -O1 为默认值,-O3 优化级别最高。
-g
只是编译器,在编译的时候,产生调试信息。
-gstabs
此选项以 stabs 格式声称调试信息, 但是不包括 gdb 调试信息。
-gstabs+
此选项以 stabs 格式声称调试信息, 并且包含仅供 gdb 使用的额外调试信息。
-ggdb
此选项将尽可能的生成 gdb 的可以使用的调试信息。
-static
此选项将禁止使用动态库,所以,编译出来的东西,一般都很大,也不需要什么动态连接库,就可以运行。
-share
此选项将尽量使用动态库,所以生成文件比较小,但是需要系统由动态库。
-x language filename
设定文件所使用的语言, 使后缀名无效, 对以后的多个有效。也就是根据约定 C 语言的后缀名称是 .c 的,而 C++ 的后缀名是 .C 或者 .cpp, 如果你很个性,决定你的 C 代码文件的后缀名是 .pig 哈哈,那你就要用这个参数, 这个参数对他后面的文件名都起作用,除非到了下一个参数的使用。可以使用的参数吗有下面的这些:'c', 'objective-c', 'c-header', 'c++', 'cpp-output', 'assembler', 与 'assembler-with-cpp'。
看到英文,应该可以理解的。
例子用法:gcc -x c hello.pig
-x none filename
关掉上一个选项,也就是让gcc根据文件名后缀,自动识别文件类型 。
例子用法:gcc -x c hello.pig -x none hello2.c
-c
只激活预处理,编译,和汇编,也就是他只把程序做成obj文件
例子用法:
gcc -c hello.c他将生成 .o 的 obj 文件
-S
只激活预处理和编译,就是指把文件编译成为汇编代码。
例子用法:
gcc -S hello.c他将生成 .s 的汇编代码,你可以用文本编辑器察看。
-E
只激活预处理,这个不生成文件, 你需要把它重定向到一个输出文件里面。
例子用法:
gcc -E hello.c > pianoapan.txtgcc -E hello.c | more慢慢看吧, 一个 hello word 也要与处理成800行的代码。
-o
制定目标名称, 默认的时候, gcc 编译出来的文件是 a.out, 很难听, 如果你和我有同感,改掉它, 哈哈。
例子用法:
gcc -o hello.exe hello.c gcc -o hello.asm -S hello.c
-pipe
使用管道代替编译中临时文件, 在使用非 gnu 汇编工具的时候, 可能有些问题。
gcc -pipe -o hello.exe hello.c
-ansi
关闭 gnu c中与 ansi c 不兼容的特性, 激活 ansi c 的专有特性(包括禁止一些 asm inline typeof 关键字, 以及 UNIX,vax 等预处理宏)。
-fno-asm
此选项实现 ansi 选项的功能的一部分,它禁止将 asm, inline 和 typeof 用作关键字。
-fno-strict-prototype
只对 g++ 起作用, 使用这个选项, g++ 将对不带参数的函数,都认为是没有显式的对参数的个数和类型说明,而不是没有参数。
而 gcc 无论是否使用这个参数, 都将对没有带参数的函数, 认为城没有显式说明的类型。
-fthis-is-varialble
就是向传统 c++ 看齐, 可以使用 this 当一般变量使用。
-fcond-mismatch
允许条件表达式的第二和第三参数类型不匹配, 表达式的值将为 void 类型。
-funsigned-char 、-fno-signed-char、-fsigned-char 、-fno-unsigned-char
这四个参数是对 char 类型进行设置, 决定将 char 类型设置成 unsigned char(前两个参数)或者 signed char(后两个参数)。
-include file包含某个代码,简单来说,就是便以某个文件,需要另一个文件的时候,就可以用它设定,功能就相当于在代码中使用 #include<filename>。
例子用法:
gcc hello.c -include /root/pianopan.h -imacros file
将 file 文件的宏, 扩展到 gcc/g++ 的输入文件, 宏定义本身并不出现在输入文件中。
-Dmacro
相当于 C 语言中的 #define macro
-Dmacro=defn
相当于 C 语言中的 #define macro=defn
-Umacro
相当于 C 语言中的 #undef macro
-undef
取消对任何非标准宏的定义
-Idir
在你是用 #include "file" 的时候, gcc/g++ 会先在当前目录查找你所制定的头文件, 如果没有找到, 他回到默认的头文件目录找, 如果使用 -I 制定了目录,他会先在你所制定的目录查找, 然后再按常规的顺序去找。
对于 #include<file>, gcc/g++ 会到 -I 制定的目录查找, 查找不到, 然后将到系统的默认的头文件目录查找 。
-I-
就是取消前一个参数的功能, 所以一般在 -Idir 之后使用。
-idirafter dir
在 -I 的目录里面查找失败, 讲到这个目录里面查找。
-iprefix prefix 、-iwithprefix dir
一般一起使用, 当 -I 的目录查找失败, 会到 prefix+dir 下查找
-nostdinc
使编译器不再系统默认的头文件目录里面找头文件, 一般和 -I 联合使用,明确限定头文件的位置。
-nostdin C++
规定不在 g++ 指定的标准路经中搜索, 但仍在其他路径中搜索, 此选项在创 libg++ 库使用 。
-traditional
试图让编译器支持传统的C语言特性
小结
好了,小伙伴们,这次就先到这里吧。希望本文对小伙伴们有所帮助,喜欢的记得点赞、分享。
END